/*
* Cogl
*
* An object oriented GL/GLES Abstraction/Utility Layer
*
* Copyright (C) 2010,2013 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
*
*
* Authors:
* Neil Roberts
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#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 */