mutter/cogl/driver/gl/cogl-pipeline-vertend-glsl.c
Robert Bragg a0441778ad This re-licenses Cogl 1.18 under the MIT license
Since the Cogl 1.18 branch is actively maintained in parallel with the
master branch; this is a counter part to commit 1b83ef938fc16b which
re-licensed the master branch to use the MIT license.

This re-licensing is a follow up to the proposal that was sent to the
Cogl mailing list:
http://lists.freedesktop.org/archives/cogl/2013-December/001465.html

Note: there was a copyright assignment policy in place for Clutter (and
therefore Cogl which was part of Clutter at the time) until the 11th of
June 2010 and so we only checked the details after that point (commit
0bbf50f905)

For each file, authors were identified via this Git command:
$ git blame -p -C -C -C20 -M -M10  0bbf50f905..HEAD

We received blanket approvals for re-licensing all Red Hat and Collabora
contributions which reduced how many people needed to be contacted
individually:
- http://lists.freedesktop.org/archives/cogl/2013-December/001470.html
- http://lists.freedesktop.org/archives/cogl/2014-January/001536.html

Individual approval requests were sent to all the other identified authors
who all confirmed the re-license on the Cogl mailinglist:
http://lists.freedesktop.org/archives/cogl/2014-January

As well as updating the copyright header in all sources files, the
COPYING file has been updated to reflect the license change and also
document the other licenses used in Cogl such as the SGI Free Software
License B, version 2.0 and the 3-clause BSD license.

This patch was not simply cherry-picked from master; but the same
methodology was used to check the source files.
2014-02-22 02:02:53 +00:00

681 lines
23 KiB
C

/*
* Cogl
*
* A Low Level GPU Graphics and Utilities API
*
* Copyright (C) 2010,2013 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:
* Neil Roberts <neil@linux.intel.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <test-fixtures/test-unit.h>
#include "cogl-context-private.h"
#include "cogl-util-gl-private.h"
#include "cogl-pipeline-private.h"
#include "cogl-pipeline-opengl-private.h"
#ifdef COGL_PIPELINE_VERTEND_GLSL
#include "cogl-context-private.h"
#include "cogl-object-private.h"
#include "cogl-program-private.h"
#include "cogl-pipeline-vertend-glsl-private.h"
#include "cogl-pipeline-state-private.h"
#include "cogl-glsl-shader-private.h"
const CoglPipelineVertend _cogl_pipeline_glsl_vertend;
typedef struct
{
unsigned int ref_count;
GLuint gl_shader;
GString *header, *source;
CoglPipelineCacheEntry *cache_entry;
} CoglPipelineShaderState;
static CoglUserDataKey shader_state_key;
static CoglPipelineShaderState *
shader_state_new (CoglPipelineCacheEntry *cache_entry)
{
CoglPipelineShaderState *shader_state;
shader_state = g_slice_new0 (CoglPipelineShaderState);
shader_state->ref_count = 1;
shader_state->cache_entry = cache_entry;
return shader_state;
}
static CoglPipelineShaderState *
get_shader_state (CoglPipeline *pipeline)
{
return cogl_object_get_user_data (COGL_OBJECT (pipeline), &shader_state_key);
}
static void
destroy_shader_state (void *user_data,
void *instance)
{
CoglPipelineShaderState *shader_state = user_data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (shader_state->cache_entry &&
shader_state->cache_entry->pipeline != instance)
shader_state->cache_entry->usage_count--;
if (--shader_state->ref_count == 0)
{
if (shader_state->gl_shader)
GE( ctx, glDeleteShader (shader_state->gl_shader) );
g_slice_free (CoglPipelineShaderState, shader_state);
}
}
static void
set_shader_state (CoglPipeline *pipeline,
CoglPipelineShaderState *shader_state)
{
if (shader_state)
{
shader_state->ref_count++;
/* If we're not setting the state on the template pipeline then
* mark it as a usage of the pipeline cache entry */
if (shader_state->cache_entry &&
shader_state->cache_entry->pipeline != pipeline)
shader_state->cache_entry->usage_count++;
}
_cogl_object_set_user_data (COGL_OBJECT (pipeline),
&shader_state_key,
shader_state,
destroy_shader_state);
}
static void
dirty_shader_state (CoglPipeline *pipeline)
{
cogl_object_set_user_data (COGL_OBJECT (pipeline),
&shader_state_key,
NULL,
NULL);
}
GLuint
_cogl_pipeline_vertend_glsl_get_shader (CoglPipeline *pipeline)
{
CoglPipelineShaderState *shader_state = get_shader_state (pipeline);
if (shader_state)
return shader_state->gl_shader;
else
return 0;
}
static CoglPipelineSnippetList *
get_vertex_snippets (CoglPipeline *pipeline)
{
pipeline =
_cogl_pipeline_get_authority (pipeline,
COGL_PIPELINE_STATE_VERTEX_SNIPPETS);
return &pipeline->big_state->vertex_snippets;
}
static CoglPipelineSnippetList *
get_layer_vertex_snippets (CoglPipelineLayer *layer)
{
unsigned long state = COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS;
layer = _cogl_pipeline_layer_get_authority (layer, state);
return &layer->big_state->vertex_snippets;
}
static CoglBool
add_layer_declaration_cb (CoglPipelineLayer *layer,
void *user_data)
{
CoglPipelineShaderState *shader_state = user_data;
CoglTextureType texture_type =
_cogl_pipeline_layer_get_texture_type (layer);
const char *target_string;
_cogl_gl_util_get_texture_target_string (texture_type, &target_string, NULL);
g_string_append_printf (shader_state->header,
"uniform sampler%s cogl_sampler%i;\n",
target_string,
layer->index);
return TRUE;
}
static void
add_layer_declarations (CoglPipeline *pipeline,
CoglPipelineShaderState *shader_state)
{
/* We always emit sampler uniforms in case there will be custom
* layer snippets that want to sample arbitrary layers. */
_cogl_pipeline_foreach_layer_internal (pipeline,
add_layer_declaration_cb,
shader_state);
}
static void
add_global_declarations (CoglPipeline *pipeline,
CoglPipelineShaderState *shader_state)
{
CoglSnippetHook hook = COGL_SNIPPET_HOOK_VERTEX_GLOBALS;
CoglPipelineSnippetList *snippets = get_vertex_snippets (pipeline);
/* Add the global data hooks. All of the code in these snippets is
* always added and only the declarations data is used */
_cogl_pipeline_snippet_generate_declarations (shader_state->header,
hook,
snippets);
}
static void
_cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline,
int n_layers,
unsigned long pipelines_difference)
{
CoglPipelineShaderState *shader_state;
CoglPipelineCacheEntry *cache_entry = NULL;
CoglProgram *user_program = cogl_pipeline_get_user_program (pipeline);
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* Now lookup our glsl backend private state (allocating if
* necessary) */
shader_state = get_shader_state (pipeline);
if (shader_state == NULL)
{
CoglPipeline *authority;
/* Get the authority for anything affecting vertex shader
state */
authority = _cogl_pipeline_find_equivalent_parent
(pipeline,
_cogl_pipeline_get_state_for_vertex_codegen (ctx) &
~COGL_PIPELINE_STATE_LAYERS,
COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN);
shader_state = get_shader_state (authority);
if (shader_state == NULL)
{
/* Check if there is already a similar cached pipeline whose
shader state we can share */
if (G_LIKELY (!(COGL_DEBUG_ENABLED
(COGL_DEBUG_DISABLE_PROGRAM_CACHES))))
{
cache_entry =
_cogl_pipeline_cache_get_vertex_template (ctx->pipeline_cache,
authority);
shader_state = get_shader_state (cache_entry->pipeline);
}
if (shader_state)
shader_state->ref_count++;
else
shader_state = shader_state_new (cache_entry);
set_shader_state (authority, shader_state);
shader_state->ref_count--;
if (cache_entry)
set_shader_state (cache_entry->pipeline, shader_state);
}
if (authority != pipeline)
set_shader_state (pipeline, shader_state);
}
if (user_program)
{
/* If the user program contains a vertex shader then we don't need
to generate one */
if (_cogl_program_has_vertex_shader (user_program))
{
if (shader_state->gl_shader)
{
GE( ctx, glDeleteShader (shader_state->gl_shader) );
shader_state->gl_shader = 0;
}
return;
}
}
if (shader_state->gl_shader)
return;
/* If we make it here then we have a shader_state struct without a gl_shader
either because this is the first time we've encountered it or
because the user program has changed */
/* We reuse two grow-only GStrings for code-gen. One string
contains the uniform and attribute declarations while the
other contains the main function. We need two strings
because we need to dynamically declare attributes as the
add_layer callback is invoked */
g_string_set_size (ctx->codegen_header_buffer, 0);
g_string_set_size (ctx->codegen_source_buffer, 0);
shader_state->header = ctx->codegen_header_buffer;
shader_state->source = ctx->codegen_source_buffer;
add_layer_declarations (pipeline, shader_state);
add_global_declarations (pipeline, shader_state);
g_string_append (shader_state->source,
"void\n"
"cogl_generated_source ()\n"
"{\n");
if (cogl_pipeline_get_per_vertex_point_size (pipeline))
g_string_append (shader_state->header,
"attribute float cogl_point_size_in;\n");
else if (!_cogl_has_private_feature
(ctx, COGL_PRIVATE_FEATURE_BUILTIN_POINT_SIZE_UNIFORM))
{
/* There is no builtin uniform for the point size on GLES2 so we
need to copy it from the custom uniform in the vertex shader
if we're not using per-vertex point sizes, however we'll only
do this if the point-size is non-zero. Toggle the point size
between zero and non-zero causes a state change which
generates a new program */
if (cogl_pipeline_get_point_size (pipeline) > 0.0f)
{
g_string_append (shader_state->header,
"uniform float cogl_point_size_in;\n");
g_string_append (shader_state->source,
" cogl_point_size_out = cogl_point_size_in;\n");
}
}
}
static CoglBool
_cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline,
CoglPipelineLayer *layer,
unsigned long layers_difference,
CoglFramebuffer *framebuffer)
{
CoglPipelineShaderState *shader_state;
CoglPipelineSnippetData snippet_data;
int layer_index = layer->index;
_COGL_GET_CONTEXT (ctx, FALSE);
shader_state = get_shader_state (pipeline);
if (shader_state->source == NULL)
return TRUE;
/* Transform the texture coordinates by the layer's user matrix.
*
* FIXME: this should avoid doing the transform if there is no user
* matrix set. This might need a separate layer state flag for
* whether there is a user matrix
*
* FIXME: we could be more clever here and try to detect if the
* fragment program is going to use the texture coordinates and
* avoid setting them if not
*/
g_string_append_printf (shader_state->header,
"vec4\n"
"cogl_real_transform_layer%i (mat4 matrix, "
"vec4 tex_coord)\n"
"{\n"
" return matrix * tex_coord;\n"
"}\n",
layer_index);
/* Wrap the layer code in any snippets that have been hooked */
memset (&snippet_data, 0, sizeof (snippet_data));
snippet_data.snippets = get_layer_vertex_snippets (layer);
snippet_data.hook = COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM;
snippet_data.chain_function = g_strdup_printf ("cogl_real_transform_layer%i",
layer_index);
snippet_data.final_name = g_strdup_printf ("cogl_transform_layer%i",
layer_index);
snippet_data.function_prefix = g_strdup_printf ("cogl_transform_layer%i",
layer_index);
snippet_data.return_type = "vec4";
snippet_data.return_variable = "cogl_tex_coord";
snippet_data.return_variable_is_argument = TRUE;
snippet_data.arguments = "cogl_matrix, cogl_tex_coord";
snippet_data.argument_declarations = "mat4 cogl_matrix, vec4 cogl_tex_coord";
snippet_data.source_buf = shader_state->header;
_cogl_pipeline_snippet_generate_code (&snippet_data);
g_free ((char *) snippet_data.chain_function);
g_free ((char *) snippet_data.final_name);
g_free ((char *) snippet_data.function_prefix);
g_string_append_printf (shader_state->source,
" cogl_tex_coord%i_out = "
"cogl_transform_layer%i (cogl_texture_matrix%i,\n"
" "
" cogl_tex_coord%i_in);\n",
layer_index,
layer_index,
layer_index,
layer_index);
return TRUE;
}
static CoglBool
_cogl_pipeline_vertend_glsl_end (CoglPipeline *pipeline,
unsigned long pipelines_difference)
{
CoglPipelineShaderState *shader_state;
_COGL_GET_CONTEXT (ctx, FALSE);
shader_state = get_shader_state (pipeline);
if (shader_state->source)
{
const char *source_strings[2];
GLint lengths[2];
GLint compile_status;
GLuint shader;
CoglPipelineSnippetData snippet_data;
CoglPipelineSnippetList *vertex_snippets;
CoglBool has_per_vertex_point_size =
cogl_pipeline_get_per_vertex_point_size (pipeline);
COGL_STATIC_COUNTER (vertend_glsl_compile_counter,
"glsl vertex compile counter",
"Increments each time a new GLSL "
"vertex shader is compiled",
0 /* no application private data */);
COGL_COUNTER_INC (_cogl_uprof_context, vertend_glsl_compile_counter);
g_string_append (shader_state->header,
"void\n"
"cogl_real_vertex_transform ()\n"
"{\n"
" cogl_position_out = "
"cogl_modelview_projection_matrix * "
"cogl_position_in;\n"
"}\n");
g_string_append (shader_state->source,
" cogl_vertex_transform ();\n");
if (has_per_vertex_point_size)
{
g_string_append (shader_state->header,
"void\n"
"cogl_real_point_size_calculation ()\n"
"{\n"
" cogl_point_size_out = cogl_point_size_in;\n"
"}\n");
g_string_append (shader_state->source,
" cogl_point_size_calculation ();\n");
}
g_string_append (shader_state->source,
" cogl_color_out = cogl_color_in;\n"
"}\n");
vertex_snippets = get_vertex_snippets (pipeline);
/* Add hooks for the vertex transform part */
memset (&snippet_data, 0, sizeof (snippet_data));
snippet_data.snippets = vertex_snippets;
snippet_data.hook = COGL_SNIPPET_HOOK_VERTEX_TRANSFORM;
snippet_data.chain_function = "cogl_real_vertex_transform";
snippet_data.final_name = "cogl_vertex_transform";
snippet_data.function_prefix = "cogl_vertex_transform";
snippet_data.source_buf = shader_state->header;
_cogl_pipeline_snippet_generate_code (&snippet_data);
/* Add hooks for the point size calculation part */
if (has_per_vertex_point_size)
{
memset (&snippet_data, 0, sizeof (snippet_data));
snippet_data.snippets = vertex_snippets;
snippet_data.hook = COGL_SNIPPET_HOOK_POINT_SIZE;
snippet_data.chain_function = "cogl_real_point_size_calculation";
snippet_data.final_name = "cogl_point_size_calculation";
snippet_data.function_prefix = "cogl_point_size_calculation";
snippet_data.source_buf = shader_state->header;
_cogl_pipeline_snippet_generate_code (&snippet_data);
}
/* Add all of the hooks for vertex processing */
memset (&snippet_data, 0, sizeof (snippet_data));
snippet_data.snippets = vertex_snippets;
snippet_data.hook = COGL_SNIPPET_HOOK_VERTEX;
snippet_data.chain_function = "cogl_generated_source";
snippet_data.final_name = "cogl_vertex_hook";
snippet_data.function_prefix = "cogl_vertex_hook";
snippet_data.source_buf = shader_state->source;
_cogl_pipeline_snippet_generate_code (&snippet_data);
g_string_append (shader_state->source,
"void\n"
"main ()\n"
"{\n"
" cogl_vertex_hook ();\n");
/* If there are any snippets then we can't rely on the
projection matrix to flip the rendering for offscreen buffers
so we'll need to flip it using an extra statement and a
uniform */
if (_cogl_pipeline_has_vertex_snippets (pipeline))
{
g_string_append (shader_state->header,
"uniform vec4 _cogl_flip_vector;\n");
g_string_append (shader_state->source,
" cogl_position_out *= _cogl_flip_vector;\n");
}
g_string_append (shader_state->source,
"}\n");
GE_RET( shader, ctx, glCreateShader (GL_VERTEX_SHADER) );
lengths[0] = shader_state->header->len;
source_strings[0] = shader_state->header->str;
lengths[1] = shader_state->source->len;
source_strings[1] = shader_state->source->str;
_cogl_glsl_shader_set_source_with_boilerplate (ctx,
shader, GL_VERTEX_SHADER,
pipeline,
2, /* count */
source_strings, lengths);
GE( ctx, glCompileShader (shader) );
GE( ctx, glGetShaderiv (shader, GL_COMPILE_STATUS, &compile_status) );
if (!compile_status)
{
GLint len = 0;
char *shader_log;
GE( ctx, glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &len) );
shader_log = g_alloca (len);
GE( ctx, glGetShaderInfoLog (shader, len, &len, shader_log) );
g_warning ("Shader compilation failed:\n%s", shader_log);
}
shader_state->header = NULL;
shader_state->source = NULL;
shader_state->gl_shader = shader;
}
#ifdef HAVE_COGL_GL
if (_cogl_has_private_feature
(ctx, COGL_PRIVATE_FEATURE_BUILTIN_POINT_SIZE_UNIFORM) &&
(pipelines_difference & COGL_PIPELINE_STATE_POINT_SIZE))
{
CoglPipeline *authority =
_cogl_pipeline_get_authority (pipeline, COGL_PIPELINE_STATE_POINT_SIZE);
if (authority->big_state->point_size > 0.0f)
GE( ctx, glPointSize (authority->big_state->point_size) );
}
#endif /* HAVE_COGL_GL */
return TRUE;
}
static void
_cogl_pipeline_vertend_glsl_pre_change_notify (CoglPipeline *pipeline,
CoglPipelineState change,
const CoglColor *new_color)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if ((change & _cogl_pipeline_get_state_for_vertex_codegen (ctx)))
dirty_shader_state (pipeline);
}
/* NB: layers are considered immutable once they have any dependants
* so although multiple pipelines can end up depending on a single
* static layer, we can guarantee that if a layer is being *changed*
* then it can only have one pipeline depending on it.
*
* XXX: Don't forget this is *pre* change, we can't read the new value
* yet!
*/
static void
_cogl_pipeline_vertend_glsl_layer_pre_change_notify (
CoglPipeline *owner,
CoglPipelineLayer *layer,
CoglPipelineLayerState change)
{
CoglPipelineShaderState *shader_state;
shader_state = get_shader_state (owner);
if (!shader_state)
return;
if ((change & COGL_PIPELINE_LAYER_STATE_AFFECTS_VERTEX_CODEGEN))
{
dirty_shader_state (owner);
return;
}
/* 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. */
}
const CoglPipelineVertend _cogl_pipeline_glsl_vertend =
{
_cogl_pipeline_vertend_glsl_start,
_cogl_pipeline_vertend_glsl_add_layer,
_cogl_pipeline_vertend_glsl_end,
_cogl_pipeline_vertend_glsl_pre_change_notify,
_cogl_pipeline_vertend_glsl_layer_pre_change_notify
};
UNIT_TEST (check_point_size_shader,
0 /* no requirements */,
0 /* no failure cases */)
{
CoglPipeline *pipelines[4];
CoglPipelineShaderState *shader_states[G_N_ELEMENTS (pipelines)];
int i;
/* Default pipeline with zero point size */
pipelines[0] = cogl_pipeline_new (test_ctx);
/* Point size 1 */
pipelines[1] = cogl_pipeline_new (test_ctx);
cogl_pipeline_set_point_size (pipelines[1], 1.0f);
/* Point size 2 */
pipelines[2] = cogl_pipeline_new (test_ctx);
cogl_pipeline_set_point_size (pipelines[2], 2.0f);
/* Same as the first pipeline, but reached by restoring the old
* state from a copy */
pipelines[3] = cogl_pipeline_copy (pipelines[1]);
cogl_pipeline_set_point_size (pipelines[3], 0.0f);
/* Draw something with all of the pipelines to make sure their state
* is flushed */
for (i = 0; i < G_N_ELEMENTS (pipelines); i++)
cogl_framebuffer_draw_rectangle (test_fb,
pipelines[i],
0.0f, 0.0f,
10.0f, 10.0f);
cogl_framebuffer_finish (test_fb);
/* Get all of the shader states. These might be NULL if the driver
* is not using GLSL */
for (i = 0; i < G_N_ELEMENTS (pipelines); i++)
shader_states[i] = get_shader_state (pipelines[i]);
/* If the first two pipelines are using GLSL then they should have
* the same shader unless there is no builtin uniform for the point
* size */
if (shader_states[0])
{
if (_cogl_has_private_feature
(test_ctx, COGL_PRIVATE_FEATURE_BUILTIN_POINT_SIZE_UNIFORM))
g_assert (shader_states[0] == shader_states[1]);
else
g_assert (shader_states[0] != shader_states[1]);
}
/* The second and third pipelines should always have the same shader
* state because only toggling between zero and non-zero should
* change the shader */
g_assert (shader_states[1] == shader_states[2]);
/* The fourth pipeline should be exactly the same as the first */
g_assert (shader_states[0] == shader_states[3]);
}
#endif /* COGL_PIPELINE_VERTEND_GLSL */