mutter/cogl/cogl/driver/gl/cogl-pipeline-opengl.c
Jonas Ådahl b2579750a7 cogl: Remove legacy OpenGL driver support
This means the two Cogl drivers left are OpenGL >= 3.1 and GLES >= 2.0.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2672>
2023-02-21 18:09:28 +00:00

1254 lines
42 KiB
C

/*
* Cogl
*
* A Low Level GPU Graphics and Utilities API
*
* Copyright (C) 2008,2009,2010 Intel Corporation.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*
* Authors:
* Robert Bragg <robert@linux.intel.com>
*/
#include "cogl-config.h"
#include "cogl-debug.h"
#include "cogl-pipeline-private.h"
#include "cogl-context-private.h"
#include "cogl-texture-private.h"
#include "cogl-framebuffer-private.h"
#include "cogl-offscreen.h"
#include "driver/gl/cogl-util-gl-private.h"
#include "driver/gl/cogl-pipeline-opengl-private.h"
#include "driver/gl/cogl-texture-gl-private.h"
#include "driver/gl/cogl-pipeline-progend-glsl-private.h"
#include <glib.h>
#include <string.h>
/*
* GL/GLES compatibility defines for pipeline thingies:
*/
/* These aren't defined in the GLES headers */
#ifndef GL_POINT_SPRITE
#define GL_POINT_SPRITE 0x8861
#endif
#ifndef GL_COORD_REPLACE
#define GL_COORD_REPLACE 0x8862
#endif
#ifndef GL_CLAMP_TO_BORDER
#define GL_CLAMP_TO_BORDER 0x812d
#endif
static void
texture_unit_init (CoglContext *ctx,
CoglTextureUnit *unit,
int index_)
{
unit->index = index_;
unit->enabled_gl_target = 0;
unit->gl_texture = 0;
unit->gl_target = 0;
unit->dirty_gl_texture = FALSE;
unit->matrix_stack = cogl_matrix_stack_new (ctx);
unit->layer = NULL;
unit->layer_changes_since_flush = 0;
unit->texture_storage_changed = FALSE;
}
static void
texture_unit_free (CoglTextureUnit *unit)
{
if (unit->layer)
cogl_object_unref (unit->layer);
cogl_object_unref (unit->matrix_stack);
}
CoglTextureUnit *
_cogl_get_texture_unit (int index_)
{
_COGL_GET_CONTEXT (ctx, NULL);
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
if (glctx->texture_units->len < (index_ + 1))
{
int i;
int prev_len = glctx->texture_units->len;
glctx->texture_units = g_array_set_size (glctx->texture_units,
index_ + 1);
for (i = prev_len; i <= index_; i++)
{
CoglTextureUnit *unit =
&g_array_index (glctx->texture_units, CoglTextureUnit, i);
texture_unit_init (ctx, unit, i);
}
}
return &g_array_index (glctx->texture_units, CoglTextureUnit, index_);
}
void
_cogl_destroy_texture_units (CoglContext *ctx)
{
int i;
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
for (i = 0; i < glctx->texture_units->len; i++)
{
CoglTextureUnit *unit =
&g_array_index (glctx->texture_units, CoglTextureUnit, i);
texture_unit_free (unit);
}
g_array_free (glctx->texture_units, TRUE);
}
void
_cogl_set_active_texture_unit (int unit_index)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
if (glctx->active_texture_unit != unit_index)
{
GE (ctx, glActiveTexture (GL_TEXTURE0 + unit_index));
glctx->active_texture_unit = unit_index;
}
}
/* Note: _cogl_bind_gl_texture_transient conceptually has slightly
* different semantics to OpenGL's glBindTexture because Cogl never
* cares about tracking multiple textures bound to different targets
* on the same texture unit.
*
* glBindTexture lets you bind multiple textures to a single texture
* unit if they are bound to different targets. So it does something
* like:
* unit->current_texture[target] = texture;
*
* Cogl only lets you associate one texture with the currently active
* texture unit, so the target is basically a redundant parameter
* that's implicitly set on that texture.
*
* Technically this is just a thin wrapper around glBindTexture so
* actually it does have the GL semantics but it seems worth
* mentioning the conceptual difference in case anyone wonders why we
* don't associate the gl_texture with a gl_target in the
* CoglTextureUnit.
*/
void
_cogl_bind_gl_texture_transient (GLenum gl_target,
GLuint gl_texture)
{
CoglTextureUnit *unit;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* We choose to always make texture unit 1 active for transient
* binds 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.
*/
_cogl_set_active_texture_unit (1);
unit = _cogl_get_texture_unit (1);
if (unit->gl_texture == gl_texture && !unit->dirty_gl_texture)
return;
GE (ctx, glBindTexture (gl_target, gl_texture));
unit->dirty_gl_texture = TRUE;
}
void
_cogl_delete_gl_texture (GLuint gl_texture)
{
int i;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
for (i = 0; i < glctx->texture_units->len; i++)
{
CoglTextureUnit *unit =
&g_array_index (glctx->texture_units, CoglTextureUnit, i);
if (unit->gl_texture == gl_texture)
{
unit->gl_texture = 0;
unit->gl_target = 0;
unit->dirty_gl_texture = FALSE;
}
}
GE (ctx, glDeleteTextures (1, &gl_texture));
}
/* Whenever the underlying GL texture storage of a CoglTexture is
* changed (e.g. due to migration out of a texture atlas) then we are
* notified. This lets us ensure that we reflush that texture's state
* if it is reused again with the same texture unit.
*/
void
_cogl_pipeline_texture_storage_change_notify (CoglTexture *texture)
{
int i;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
for (i = 0; i < glctx->texture_units->len; i++)
{
CoglTextureUnit *unit =
&g_array_index (glctx->texture_units, CoglTextureUnit, i);
if (unit->layer &&
_cogl_pipeline_layer_get_texture (unit->layer) == texture)
unit->texture_storage_changed = TRUE;
/* NB: the texture may be bound to multiple texture units so
* we continue to check the rest */
}
}
#if defined(HAVE_COGL_GLES2) || defined(HAVE_COGL_GL)
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
flush_depth_state (CoglContext *ctx,
CoglDepthState *depth_state)
{
gboolean depth_writing_enabled = depth_state->write_enabled;
if (ctx->current_draw_buffer)
{
depth_writing_enabled &=
cogl_framebuffer_get_depth_write_enabled (ctx->current_draw_buffer);
}
if (ctx->depth_test_enabled_cache != depth_state->test_enabled)
{
if (depth_state->test_enabled == TRUE)
{
GE (ctx, glEnable (GL_DEPTH_TEST));
if (ctx->current_draw_buffer)
cogl_framebuffer_set_depth_buffer_clear_needed (ctx->current_draw_buffer);
}
else
GE (ctx, glDisable (GL_DEPTH_TEST));
ctx->depth_test_enabled_cache = depth_state->test_enabled;
}
if (ctx->depth_test_function_cache != depth_state->test_function &&
depth_state->test_enabled == TRUE)
{
GE (ctx, glDepthFunc (depth_state->test_function));
ctx->depth_test_function_cache = depth_state->test_function;
}
if (ctx->depth_writing_enabled_cache != depth_writing_enabled)
{
GE (ctx, glDepthMask (depth_writing_enabled ?
GL_TRUE : GL_FALSE));
ctx->depth_writing_enabled_cache = depth_writing_enabled;
}
if ((ctx->depth_range_near_cache != depth_state->range_near ||
ctx->depth_range_far_cache != depth_state->range_far))
{
if (ctx->driver == COGL_DRIVER_GLES2)
GE (ctx, glDepthRangef (depth_state->range_near,
depth_state->range_far));
else
GE (ctx, glDepthRange (depth_state->range_near,
depth_state->range_far));
ctx->depth_range_near_cache = depth_state->range_near;
ctx->depth_range_far_cache = depth_state->range_far;
}
}
static void
_cogl_pipeline_flush_color_blend_alpha_depth_state (
CoglPipeline *pipeline,
unsigned long pipelines_difference,
gboolean with_color_attrib)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (pipelines_difference & COGL_PIPELINE_STATE_BLEND)
{
CoglPipeline *authority =
_cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_BLEND);
CoglPipelineBlendState *blend_state =
&authority->big_state->blend_state;
#if defined(HAVE_COGL_GLES2) || defined(HAVE_COGL_GL)
if (blend_factor_uses_constant (blend_state->blend_src_factor_rgb) ||
blend_factor_uses_constant (blend_state
->blend_src_factor_alpha) ||
blend_factor_uses_constant (blend_state->blend_dst_factor_rgb) ||
blend_factor_uses_constant (blend_state->blend_dst_factor_alpha))
{
float red =
cogl_color_get_red_float (&blend_state->blend_constant);
float green =
cogl_color_get_green_float (&blend_state->blend_constant);
float blue =
cogl_color_get_blue_float (&blend_state->blend_constant);
float alpha =
cogl_color_get_alpha_float (&blend_state->blend_constant);
GE (ctx, glBlendColor (red, green, blue, alpha));
}
GE (ctx, glBlendEquationSeparate (blend_state->blend_equation_rgb,
blend_state->blend_equation_alpha));
GE (ctx, glBlendFuncSeparate (blend_state->blend_src_factor_rgb,
blend_state->blend_dst_factor_rgb,
blend_state->blend_src_factor_alpha,
blend_state->blend_dst_factor_alpha));
}
#endif
if (pipelines_difference & COGL_PIPELINE_STATE_DEPTH)
{
CoglPipeline *authority =
_cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_DEPTH);
CoglDepthState *depth_state = &authority->big_state->depth_state;
flush_depth_state (ctx, depth_state);
}
if (pipelines_difference & COGL_PIPELINE_STATE_CULL_FACE)
{
CoglPipeline *authority =
_cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_CULL_FACE);
CoglPipelineCullFaceState *cull_face_state
= &authority->big_state->cull_face_state;
if (cull_face_state->mode == COGL_PIPELINE_CULL_FACE_MODE_NONE)
GE( ctx, glDisable (GL_CULL_FACE) );
else
{
gboolean invert_winding;
GE( ctx, glEnable (GL_CULL_FACE) );
switch (cull_face_state->mode)
{
case COGL_PIPELINE_CULL_FACE_MODE_NONE:
g_assert_not_reached ();
case COGL_PIPELINE_CULL_FACE_MODE_FRONT:
GE( ctx, glCullFace (GL_FRONT) );
break;
case COGL_PIPELINE_CULL_FACE_MODE_BACK:
GE( ctx, glCullFace (GL_BACK) );
break;
case COGL_PIPELINE_CULL_FACE_MODE_BOTH:
GE( ctx, glCullFace (GL_FRONT_AND_BACK) );
break;
}
invert_winding =
cogl_framebuffer_is_y_flipped (ctx->current_draw_buffer);
switch (cull_face_state->front_winding)
{
case COGL_WINDING_CLOCKWISE:
GE( ctx, glFrontFace (invert_winding ? GL_CCW : GL_CW) );
break;
case COGL_WINDING_COUNTER_CLOCKWISE:
GE( ctx, glFrontFace (invert_winding ? GL_CW : GL_CCW) );
break;
}
}
}
if (pipeline->real_blend_enable != ctx->gl_blend_enable_cache)
{
if (pipeline->real_blend_enable)
GE (ctx, glEnable (GL_BLEND));
else
GE (ctx, glDisable (GL_BLEND));
/* XXX: we shouldn't update any other blend state if blending
* is disabled! */
ctx->gl_blend_enable_cache = pipeline->real_blend_enable;
}
}
static int
get_max_activateable_texture_units (void)
{
_COGL_GET_CONTEXT (ctx, 0);
if (G_UNLIKELY (ctx->max_activateable_texture_units == -1))
{
GLint values[3];
int n_values = 0;
int i;
#ifdef HAVE_COGL_GL
if (ctx->driver != COGL_DRIVER_GLES2)
{
/* GL_MAX_TEXTURE_COORDS defines the number of texture coordinates
* that can be uploaded (but doesn't necessarily relate to how many
* texture images can be sampled) */
GE (ctx, glGetIntegerv (GL_MAX_TEXTURE_COORDS, values + n_values++));
GE (ctx, glGetIntegerv (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
values + n_values++));
}
#endif /* HAVE_COGL_GL */
#ifdef HAVE_COGL_GLES2
if (ctx->driver == COGL_DRIVER_GLES2)
{
GE (ctx, glGetIntegerv (GL_MAX_VERTEX_ATTRIBS, values + n_values));
/* Two of the vertex attribs need to be used for the position
and color */
values[n_values++] -= 2;
GE (ctx, glGetIntegerv (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
values + n_values++));
}
#endif
g_assert (n_values <= G_N_ELEMENTS (values) &&
n_values > 0);
/* Use the maximum value */
ctx->max_activateable_texture_units = values[0];
for (i = 1; i < n_values; i++)
ctx->max_activateable_texture_units =
MAX (values[i], ctx->max_activateable_texture_units);
}
return ctx->max_activateable_texture_units;
}
typedef struct
{
int i;
unsigned long *layer_differences;
} CoglPipelineFlushLayerState;
static gboolean
flush_layers_common_gl_state_cb (CoglPipelineLayer *layer, void *user_data)
{
CoglPipelineFlushLayerState *flush_state = user_data;
int unit_index = flush_state->i;
CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index);
unsigned long layers_difference =
flush_state->layer_differences[unit_index];
_COGL_GET_CONTEXT (ctx, FALSE);
/* 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 ()))
{
static gboolean shown_warning = FALSE;
if (!shown_warning)
{
g_warning ("Your hardware does not have enough texture units"
"to handle this many texture layers");
shown_warning = TRUE;
}
return FALSE;
}
if (layers_difference & COGL_PIPELINE_LAYER_STATE_TEXTURE_DATA)
{
CoglTexture *texture = _cogl_pipeline_layer_get_texture_real (layer);
GLuint gl_texture;
GLenum gl_target;
if (texture == NULL)
texture = COGL_TEXTURE (ctx->default_gl_texture_2d_tex);
cogl_texture_get_gl_texture (texture,
&gl_texture,
&gl_target);
_cogl_set_active_texture_unit (unit_index);
/* NB: There are several Cogl components and some code in
* Clutter that will temporarily bind arbitrary GL textures to
* query and modify texture object parameters. If you look at
* _cogl_bind_gl_texture_transient() you can see we make sure
* that such code always binds to texture unit 1 which means we
* can't rely on the unit->gl_texture state if unit->index == 1.
*
* Because texture unit 1 is a bit special we actually defer any
* necessary glBindTexture for it until the end of
* _cogl_pipeline_flush_gl_state().
*
* NB: we get notified whenever glDeleteTextures is used (see
* _cogl_delete_gl_texture()) where we invalidate
* unit->gl_texture references to deleted textures so it's safe
* to compare unit->gl_texture with gl_texture. (Without the
* hook it would be possible to delete a GL texture and create a
* new one with the same name and comparing unit->gl_texture and
* gl_texture wouldn't detect that.)
*
* NB: for foreign textures we don't know how the deletion of
* the GL texture objects correspond to the deletion of the
* CoglTextures so if there was previously a foreign texture
* associated with the texture unit then we can't assume that we
* aren't seeing a recycled texture name so we have to bind.
*/
if (unit->gl_texture != gl_texture)
{
if (unit_index == 1)
unit->dirty_gl_texture = TRUE;
else
GE (ctx, glBindTexture (gl_target, gl_texture));
unit->gl_texture = gl_texture;
unit->gl_target = gl_target;
}
/* The texture_storage_changed boolean indicates if the
* CoglTexture's underlying GL texture storage has changed since
* it was flushed to the texture unit. We've just flushed the
* latest state so we can reset this. */
unit->texture_storage_changed = FALSE;
}
if ((layers_difference & COGL_PIPELINE_LAYER_STATE_SAMPLER) &&
_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_SAMPLER_OBJECTS))
{
const CoglSamplerCacheEntry *sampler_state;
sampler_state = _cogl_pipeline_layer_get_sampler_state (layer);
GE( ctx, glBindSampler (unit_index, sampler_state->sampler_object) );
}
cogl_object_ref (layer);
if (unit->layer != NULL)
cogl_object_unref (unit->layer);
unit->layer = layer;
unit->layer_changes_since_flush = 0;
flush_state->i++;
return TRUE;
}
static void
_cogl_pipeline_flush_common_gl_state (CoglPipeline *pipeline,
unsigned long pipelines_difference,
unsigned long *layer_differences,
gboolean with_color_attrib)
{
CoglPipelineFlushLayerState state;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
_cogl_pipeline_flush_color_blend_alpha_depth_state (pipeline,
pipelines_difference,
with_color_attrib);
state.i = 0;
state.layer_differences = layer_differences;
_cogl_pipeline_foreach_layer_internal (pipeline,
flush_layers_common_gl_state_cb,
&state);
}
/* 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_pipeline_layer_forward_wrap_modes (CoglPipelineLayer *layer,
CoglTexture *texture)
{
CoglSamplerCacheWrapMode wrap_mode_s, wrap_mode_t;
GLenum gl_wrap_mode_s, gl_wrap_mode_t;
if (texture == NULL)
return;
_cogl_pipeline_layer_get_wrap_modes (layer,
&wrap_mode_s,
&wrap_mode_t);
/* 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_s == COGL_SAMPLER_CACHE_WRAP_MODE_AUTOMATIC)
gl_wrap_mode_s = GL_CLAMP_TO_EDGE;
else
gl_wrap_mode_s = wrap_mode_s;
if (wrap_mode_t == COGL_SAMPLER_CACHE_WRAP_MODE_AUTOMATIC)
gl_wrap_mode_t = GL_CLAMP_TO_EDGE;
else
gl_wrap_mode_t = wrap_mode_t;
_cogl_texture_gl_flush_legacy_texobj_wrap_modes (texture,
gl_wrap_mode_s,
gl_wrap_mode_t);
}
void
_cogl_sampler_gl_init (CoglContext *context, CoglSamplerCacheEntry *entry)
{
if (_cogl_has_private_feature (context,
COGL_PRIVATE_FEATURE_SAMPLER_OBJECTS))
{
GE( context, glGenSamplers (1, &entry->sampler_object) );
GE( context, glSamplerParameteri (entry->sampler_object,
GL_TEXTURE_MIN_FILTER,
entry->min_filter) );
GE( context, glSamplerParameteri (entry->sampler_object,
GL_TEXTURE_MAG_FILTER,
entry->mag_filter) );
GE (context, glSamplerParameteri (entry->sampler_object,
GL_TEXTURE_WRAP_S,
entry->wrap_mode_s) );
GE (context, glSamplerParameteri (entry->sampler_object,
GL_TEXTURE_WRAP_T,
entry->wrap_mode_t) );
/* While COGL_PRIVATE_FEATURE_SAMPLER_OBJECTS implies support for
* GL_TEXTURE_LOD_BIAS in GL, the same is not true in GLES. So check,
* and also only apply GL_TEXTURE_LOD_BIAS in mipmap modes:
*/
if (_cogl_has_private_feature (context,
COGL_PRIVATE_FEATURE_TEXTURE_LOD_BIAS) &&
entry->min_filter != GL_NEAREST &&
entry->min_filter != GL_LINEAR)
{
GLfloat bias = _cogl_texture_min_filter_get_lod_bias (entry->min_filter);
GE (context, glSamplerParameterf (entry->sampler_object,
GL_TEXTURE_LOD_BIAS,
bias));
}
}
else
{
CoglGLContext *gl_context = context->driver_context;
/* If sampler objects aren't supported then we'll invent a
unique number so that pipelines can still compare the
unique state just by comparing the sampler object
numbers */
entry->sampler_object = gl_context->next_fake_sampler_object_number++;
}
}
void
_cogl_sampler_gl_free (CoglContext *context, CoglSamplerCacheEntry *entry)
{
if (_cogl_has_private_feature (context,
COGL_PRIVATE_FEATURE_SAMPLER_OBJECTS))
GE( context, glDeleteSamplers (1, &entry->sampler_object) );
}
/* 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 pipelines with different modes.
*
* This function is bypassed in favour of sampler objects if
* GL_ARB_sampler_objects is advertised. This fallback won't work if
* the same texture is bound to multiple layers with different sampler
* state.
*/
static void
foreach_texture_unit_update_filter_and_wrap_modes (void)
{
int i;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
CoglGLContext *glctx = _cogl_driver_gl_context(ctx);
for (i = 0; i < glctx->texture_units->len; i++)
{
CoglTextureUnit *unit =
&g_array_index (glctx->texture_units, CoglTextureUnit, i);
if (unit->layer)
{
CoglTexture *texture = _cogl_pipeline_layer_get_texture (unit->layer);
if (texture != NULL)
{
CoglPipelineFilter min;
CoglPipelineFilter mag;
_cogl_pipeline_layer_get_filters (unit->layer, &min, &mag);
_cogl_texture_gl_flush_legacy_texobj_filters (texture, min, mag);
_cogl_pipeline_layer_forward_wrap_modes (unit->layer, texture);
}
}
}
}
typedef struct
{
int i;
unsigned long *layer_differences;
} CoglPipelineCompareLayersState;
static gboolean
compare_layer_differences_cb (CoglPipelineLayer *layer, void *user_data)
{
CoglPipelineCompareLayersState *state = user_data;
CoglTextureUnit *unit = _cogl_get_texture_unit (state->i);
if (unit->layer == layer)
state->layer_differences[state->i] = unit->layer_changes_since_flush;
else if (unit->layer)
{
state->layer_differences[state->i] = unit->layer_changes_since_flush;
state->layer_differences[state->i] |=
_cogl_pipeline_layer_compare_differences (layer, unit->layer);
}
else
state->layer_differences[state->i] = COGL_PIPELINE_LAYER_STATE_ALL_SPARSE;
/* XXX: There is always a possibility that a CoglTexture's
* underlying GL texture storage has been changed since it was last
* bound to a texture unit which is why we have a callback into
* _cogl_pipeline_texture_storage_change_notify whenever a textures
* underlying GL texture storage changes which will set the
* unit->texture_intern_changed flag. If we see that's been set here
* then we force an update of the texture state...
*/
if (unit->texture_storage_changed)
state->layer_differences[state->i] |=
COGL_PIPELINE_LAYER_STATE_TEXTURE_DATA;
state->i++;
return TRUE;
}
typedef struct
{
CoglFramebuffer *framebuffer;
const CoglPipelineVertend *vertend;
const CoglPipelineFragend *fragend;
CoglPipeline *pipeline;
unsigned long *layer_differences;
gboolean error_adding_layer;
gboolean added_layer;
} CoglPipelineAddLayerState;
static gboolean
vertend_add_layer_cb (CoglPipelineLayer *layer,
void *user_data)
{
CoglPipelineAddLayerState *state = user_data;
const CoglPipelineVertend *vertend = state->vertend;
CoglPipeline *pipeline = state->pipeline;
int unit_index = _cogl_pipeline_layer_get_unit_index (layer);
/* Either generate per layer code snippets or setup the
* fixed function glTexEnv for each layer... */
if (G_LIKELY (vertend->add_layer (pipeline,
layer,
state->layer_differences[unit_index],
state->framebuffer)))
state->added_layer = TRUE;
else
{
state->error_adding_layer = TRUE;
return FALSE;
}
return TRUE;
}
static gboolean
fragend_add_layer_cb (CoglPipelineLayer *layer,
void *user_data)
{
CoglPipelineAddLayerState *state = user_data;
const CoglPipelineFragend *fragend = state->fragend;
CoglPipeline *pipeline = state->pipeline;
int unit_index = _cogl_pipeline_layer_get_unit_index (layer);
/* Either generate per layer code snippets or setup the
* fixed function glTexEnv for each layer... */
if (G_LIKELY (fragend->add_layer (pipeline,
layer,
state->layer_differences[unit_index])))
state->added_layer = TRUE;
else
{
state->error_adding_layer = TRUE;
return FALSE;
}
return TRUE;
}
/*
* _cogl_pipeline_flush_gl_state:
*
* Details of override options:
* ->fallback_mask: is a bitmask of the pipeline layers that need to be
* replaced with the default, fallback textures. The fallback textures are
* fully transparent textures so they hopefully won't contribute to the
* texture combining.
*
* The intention of fallbacks is to try and preserve
* the number of layers the user is expecting so that texture coordinates
* they gave will mostly still correspond to the textures they intended, and
* have a fighting chance of looking close to their originally intended
* result.
*
* ->disable_mask: is a bitmask of the pipeline layers that will simply have
* texturing disabled. It's only really intended for disabling all layers
* > X; i.e. we'd expect to see a contiguous run of 0 starting from the LSB
* and at some point the remaining bits flip to 1. It might work to disable
* arbitrary layers; though I'm not sure a.t.m how OpenGL would take to
* that.
*
* The intention of the disable_mask is for emitting geometry when the user
* hasn't supplied enough texture coordinates for all the layers and it's
* not possible to auto generate default texture coordinates for those
* layers.
*
* ->layer0_override_texture: forcibly tells us to bind this GL texture name for
* layer 0 instead of plucking the gl_texture from the CoglTexture of layer
* 0.
*
* 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 pipeline
* forcing the GL texture of each slice in turn.
*
* ->wrap_mode_overrides: overrides the wrap modes set on each
* layer. This is used to implement the automatic wrap mode.
*
* XXX: It might also help if we could specify a texture matrix for code
* dealing with slicing that would be multiplied with the users own matrix.
*
* Normally texture coords in the range [0, 1] refer to the extents of the
* texture, but when your GL texture represents a slice of the real texture
* (from the users POV) then a texture matrix would be a neat way of
* transforming the mapping for each slice.
*
* Currently for textured rectangles we manually calculate the texture
* coords for each slice based on the users given coords, but this solution
* isn't ideal.
*/
void
_cogl_pipeline_flush_gl_state (CoglContext *ctx,
CoglPipeline *pipeline,
CoglFramebuffer *framebuffer,
gboolean with_color_attrib,
gboolean unknown_color_alpha)
{
CoglPipeline *current_pipeline = ctx->current_pipeline;
unsigned long pipelines_difference;
int n_layers;
unsigned long *layer_differences;
CoglTextureUnit *unit1;
const CoglPipelineProgend *progend;
COGL_STATIC_TIMER (pipeline_flush_timer,
"Mainloop", /* parent */
"Material Flush",
"The time spent flushing material state",
0 /* no application private data */);
COGL_TIMER_START (_cogl_uprof_context, pipeline_flush_timer);
/* Bail out asap if we've been asked to re-flush the already current
* pipeline and we can see the pipeline hasn't changed */
if (current_pipeline == pipeline &&
ctx->current_pipeline_age == pipeline->age &&
ctx->current_pipeline_with_color_attrib == with_color_attrib &&
ctx->current_pipeline_unknown_color_alpha == unknown_color_alpha)
goto done;
else
{
/* Update derived state (currently just the 'real_blend_enable'
* state) and determine a mask of state that differs between the
* current pipeline and the one we are flushing.
*
* Note updating the derived state is done before doing any
* pipeline comparisons so that we can correctly compare the
* 'real_blend_enable' state itself.
*/
if (current_pipeline == pipeline)
{
pipelines_difference = ctx->current_pipeline_changes_since_flush;
if (pipelines_difference & COGL_PIPELINE_STATE_AFFECTS_BLENDING ||
pipeline->unknown_color_alpha != unknown_color_alpha)
{
gboolean save_real_blend_enable = pipeline->real_blend_enable;
_cogl_pipeline_update_real_blend_enable (pipeline,
unknown_color_alpha);
if (save_real_blend_enable != pipeline->real_blend_enable)
pipelines_difference |= COGL_PIPELINE_STATE_REAL_BLEND_ENABLE;
}
}
else if (current_pipeline)
{
pipelines_difference = ctx->current_pipeline_changes_since_flush;
_cogl_pipeline_update_real_blend_enable (pipeline,
unknown_color_alpha);
pipelines_difference |=
_cogl_pipeline_compare_differences (ctx->current_pipeline,
pipeline);
}
else
{
_cogl_pipeline_update_real_blend_enable (pipeline,
unknown_color_alpha);
pipelines_difference = COGL_PIPELINE_STATE_ALL;
}
}
/* Get a layer_differences mask for each layer to be flushed */
n_layers = cogl_pipeline_get_n_layers (pipeline);
if (n_layers)
{
CoglPipelineCompareLayersState state;
layer_differences = g_alloca (sizeof (unsigned long) * n_layers);
memset (layer_differences, 0, sizeof (unsigned long) * n_layers);
state.i = 0;
state.layer_differences = layer_differences;
_cogl_pipeline_foreach_layer_internal (pipeline,
compare_layer_differences_cb,
&state);
}
else
layer_differences = NULL;
/* First flush everything that's the same regardless of which
* pipeline 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
*
* Note: After _cogl_pipeline_flush_common_gl_state you can expect
* all state of the layers corresponding texture unit to be
* updated.
*/
_cogl_pipeline_flush_common_gl_state (pipeline,
pipelines_difference,
layer_differences,
with_color_attrib);
/* Now flush the fragment, vertex and program state according to the
* current progend backend.
*
* Note: Some backends may not support the current pipeline
* configuration and in that case it will report and error and we
* will look for a different backend.
*
* NB: if pipeline->progend != COGL_PIPELINE_PROGEND_UNDEFINED then
* we have previously managed to successfully flush this pipeline
* with the given progend so we will simply use that to avoid
* fallback code paths.
*/
do
{
const CoglPipelineVertend *vertend;
const CoglPipelineFragend *fragend;
CoglPipelineAddLayerState state;
progend = _cogl_pipeline_progend;
if (G_UNLIKELY (!progend->start (pipeline)))
continue;
vertend = _cogl_pipeline_vertend;
vertend->start (pipeline,
n_layers,
pipelines_difference);
state.framebuffer = framebuffer;
state.vertend = vertend;
state.pipeline = pipeline;
state.layer_differences = layer_differences;
state.error_adding_layer = FALSE;
state.added_layer = FALSE;
_cogl_pipeline_foreach_layer_internal (pipeline,
vertend_add_layer_cb,
&state);
if (G_UNLIKELY (state.error_adding_layer))
continue;
if (G_UNLIKELY (!vertend->end (pipeline, pipelines_difference)))
continue;
/* Now prepare the fragment processing state (fragend)
*
* NB: We can't combine the setup of the vertend and fragend
* since the backends that do code generation share
* ctx->codegen_source_buffer as a scratch buffer.
*/
fragend = _cogl_pipeline_fragend;
state.fragend = fragend;
fragend->start (pipeline,
n_layers,
pipelines_difference);
_cogl_pipeline_foreach_layer_internal (pipeline,
fragend_add_layer_cb,
&state);
if (G_UNLIKELY (state.error_adding_layer))
continue;
if (G_UNLIKELY (!fragend->end (pipeline, pipelines_difference)))
continue;
if (progend->end)
progend->end (pipeline, pipelines_difference);
break;
}
while (0);
/* FIXME: This reference is actually resulting in lots of
* copy-on-write reparenting because one-shot pipelines end up
* living for longer than necessary and so any later modification of
* the parent will cause a copy-on-write.
*
* XXX: The issue should largely go away when we switch to using
* weak pipelines for overrides.
*/
cogl_object_ref (pipeline);
if (ctx->current_pipeline != NULL)
cogl_object_unref (ctx->current_pipeline);
ctx->current_pipeline = pipeline;
ctx->current_pipeline_changes_since_flush = 0;
ctx->current_pipeline_with_color_attrib = with_color_attrib;
ctx->current_pipeline_unknown_color_alpha = unknown_color_alpha;
ctx->current_pipeline_age = pipeline->age;
done:
progend = _cogl_pipeline_progend;
/* We can't assume the color will be retained between flushes when
* using the glsl progend because the generic attribute values are
* not stored as part of the program object so they could be
* overridden by any attribute changes in another program */
if (!with_color_attrib)
{
int attribute;
CoglPipeline *authority =
_cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_COLOR);
int name_index = COGL_ATTRIBUTE_COLOR_NAME_INDEX;
attribute =
_cogl_pipeline_progend_glsl_get_attrib_location (pipeline, name_index);
if (attribute != -1)
GE (ctx,
glVertexAttrib4f (attribute,
cogl_color_get_red_float (&authority->color),
cogl_color_get_green_float (&authority->color),
cogl_color_get_blue_float (&authority->color),
cogl_color_get_alpha_float (&authority->color)));
}
/* Give the progend a chance to update any uniforms that might not
* depend on the material state. This is used on GLES2 to update the
* matrices */
if (progend->pre_paint)
progend->pre_paint (pipeline, framebuffer);
/* Handle the fact that OpenGL associates texture filter and wrap
* modes with the texture objects not the texture units... */
if (!_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_SAMPLER_OBJECTS))
foreach_texture_unit_update_filter_and_wrap_modes ();
/* If this pipeline 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 texture unit 1 so they can query and modify texture
* object parameters. cogl-pipeline.c (See
* _cogl_bind_gl_texture_transient)
*/
unit1 = _cogl_get_texture_unit (1);
if (cogl_pipeline_get_n_layers (pipeline) > 1 && unit1->dirty_gl_texture)
{
_cogl_set_active_texture_unit (1);
GE (ctx, glBindTexture (unit1->gl_target, unit1->gl_texture));
unit1->dirty_gl_texture = FALSE;
}
COGL_TIMER_STOP (_cogl_uprof_context, pipeline_flush_timer);
}
void
_cogl_gl_set_uniform (CoglContext *ctx,
GLint location,
const CoglBoxedValue *value)
{
switch (value->type)
{
case COGL_BOXED_NONE:
break;
case COGL_BOXED_INT:
{
const int *ptr;
if (value->count == 1)
ptr = value->v.int_value;
else
ptr = value->v.int_array;
switch (value->size)
{
case 1:
GE( ctx, glUniform1iv (location, value->count, ptr) );
break;
case 2:
GE( ctx, glUniform2iv (location, value->count, ptr) );
break;
case 3:
GE( ctx, glUniform3iv (location, value->count, ptr) );
break;
case 4:
GE( ctx, glUniform4iv (location, value->count, ptr) );
break;
}
}
break;
case COGL_BOXED_FLOAT:
{
const float *ptr;
if (value->count == 1)
ptr = value->v.float_value;
else
ptr = value->v.float_array;
switch (value->size)
{
case 1:
GE( ctx, glUniform1fv (location, value->count, ptr) );
break;
case 2:
GE( ctx, glUniform2fv (location, value->count, ptr) );
break;
case 3:
GE( ctx, glUniform3fv (location, value->count, ptr) );
break;
case 4:
GE( ctx, glUniform4fv (location, value->count, ptr) );
break;
}
}
break;
case COGL_BOXED_MATRIX:
{
const float *ptr;
if (value->count == 1)
ptr = value->v.matrix;
else
ptr = value->v.float_array;
switch (value->size)
{
case 2:
GE( ctx, glUniformMatrix2fv (location, value->count,
FALSE, ptr) );
break;
case 3:
GE( ctx, glUniformMatrix3fv (location, value->count,
FALSE, ptr) );
break;
case 4:
GE( ctx, glUniformMatrix4fv (location, value->count,
FALSE, ptr) );
break;
}
}
break;
}
}