#include "config.h" #include "gskshaderbuilderprivate.h" #include "gskdebugprivate.h" #include #include typedef struct { int program_id; GHashTable *uniform_locations; GHashTable *attribute_locations; } ShaderProgram; struct _GskShaderBuilder { GObject parent_instance; char *resource_base_path; char *vertex_preamble; char *fragment_preamble; int version; GPtrArray *defines; GPtrArray *uniforms; GPtrArray *attributes; GHashTable *programs; }; G_DEFINE_TYPE (GskShaderBuilder, gsk_shader_builder, G_TYPE_OBJECT) static void shader_program_free (gpointer data) { ShaderProgram *p = data; g_clear_pointer (&p->uniform_locations, g_hash_table_unref); g_clear_pointer (&p->attribute_locations, g_hash_table_unref); glDeleteProgram (p->program_id); g_slice_free (ShaderProgram, data); } static ShaderProgram * shader_program_new (int program_id) { ShaderProgram *p = g_slice_new (ShaderProgram); p->program_id = program_id; p->uniform_locations = g_hash_table_new (g_direct_hash, g_direct_equal); p->attribute_locations = g_hash_table_new (g_direct_hash, g_direct_equal); return p; } static void gsk_shader_builder_finalize (GObject *gobject) { GskShaderBuilder *self = GSK_SHADER_BUILDER (gobject); g_free (self->resource_base_path); g_free (self->vertex_preamble); g_free (self->fragment_preamble); g_clear_pointer (&self->defines, g_ptr_array_unref); g_clear_pointer (&self->uniforms, g_ptr_array_unref); g_clear_pointer (&self->attributes, g_ptr_array_unref); g_clear_pointer (&self->programs, g_hash_table_unref); G_OBJECT_CLASS (gsk_shader_builder_parent_class)->finalize (gobject); } static void gsk_shader_builder_class_init (GskShaderBuilderClass *klass) { G_OBJECT_CLASS (klass)->finalize = gsk_shader_builder_finalize; } static void gsk_shader_builder_init (GskShaderBuilder *self) { self->defines = g_ptr_array_new_with_free_func (g_free); self->uniforms = g_ptr_array_new_with_free_func (g_free); self->attributes = g_ptr_array_new_with_free_func (g_free); self->programs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, shader_program_free); } GskShaderBuilder * gsk_shader_builder_new (void) { return g_object_new (GSK_TYPE_SHADER_BUILDER, NULL); } void gsk_shader_builder_set_resource_base_path (GskShaderBuilder *builder, const char *base_path) { g_return_if_fail (GSK_IS_SHADER_BUILDER (builder)); g_free (builder->resource_base_path); builder->resource_base_path = g_strdup (base_path); } void gsk_shader_builder_set_vertex_preamble (GskShaderBuilder *builder, const char *vertex_preamble) { g_return_if_fail (GSK_IS_SHADER_BUILDER (builder)); g_free (builder->vertex_preamble); builder->vertex_preamble = g_strdup (vertex_preamble); } void gsk_shader_builder_set_fragment_preamble (GskShaderBuilder *builder, const char *fragment_preamble) { g_return_if_fail (GSK_IS_SHADER_BUILDER (builder)); g_free (builder->fragment_preamble); builder->fragment_preamble = g_strdup (fragment_preamble); } void gsk_shader_builder_set_version (GskShaderBuilder *builder, int version) { g_return_if_fail (GSK_IS_SHADER_BUILDER (builder)); builder->version = version; } void gsk_shader_builder_add_define (GskShaderBuilder *builder, const char *define_name, const char *define_value) { g_return_if_fail (GSK_IS_SHADER_BUILDER (builder)); g_return_if_fail (define_name != NULL && *define_name != '\0'); g_return_if_fail (define_value != NULL && *define_name != '\0'); g_ptr_array_add (builder->defines, g_strdup (define_name)); g_ptr_array_add (builder->defines, g_strdup (define_value)); } GQuark gsk_shader_builder_add_uniform (GskShaderBuilder *builder, const char *uniform_name) { g_return_val_if_fail (GSK_IS_SHADER_BUILDER (builder), 0); g_return_val_if_fail (uniform_name != NULL, 0); g_ptr_array_add (builder->uniforms, g_strdup (uniform_name)); return g_quark_from_string (uniform_name); } GQuark gsk_shader_builder_add_attribute (GskShaderBuilder *builder, const char *attribute_name) { g_return_val_if_fail (GSK_IS_SHADER_BUILDER (builder), 0); g_return_val_if_fail (attribute_name != NULL, 0); g_ptr_array_add (builder->attributes, g_strdup (attribute_name)); return g_quark_from_string (attribute_name); } static gboolean lookup_shader_code (GString *code, const char *base_path, const char *shader_file, GError **error) { GBytes *source; char *path; if (base_path != NULL) path = g_build_filename (base_path, shader_file, NULL); else path = g_strdup (shader_file); source = g_resources_lookup_data (path, 0, error); g_free (path); if (source == NULL) return FALSE; g_string_append (code, g_bytes_get_data (source, NULL)); g_bytes_unref (source); return TRUE; } static int gsk_shader_builder_compile_shader (GskShaderBuilder *builder, int shader_type, const char *shader_preamble, const char *shader_source, GError **error) { GString *code; char *source; int shader_id; int status; int i; code = g_string_new (NULL); if (builder->version > 0) { g_string_append_printf (code, "#version %d\n", builder->version); g_string_append_c (code, '\n'); } for (i = 0; i < builder->defines->len; i += 2) { const char *name = g_ptr_array_index (builder->defines, i); const char *value = g_ptr_array_index (builder->defines, i + 1); g_string_append (code, "#define"); g_string_append_c (code, ' '); g_string_append (code, name); g_string_append_c (code, ' '); g_string_append (code, value); g_string_append_c (code, '\n'); if (i == builder->defines->len - 2) g_string_append_c (code, '\n'); } if (!lookup_shader_code (code, builder->resource_base_path, shader_preamble, error)) { g_string_free (code, TRUE); return -1; } g_string_append_c (code, '\n'); if (!lookup_shader_code (code, builder->resource_base_path, shader_source, error)) { g_string_free (code, TRUE); return -1; } source = g_string_free (code, FALSE); shader_id = glCreateShader (shader_type); glShaderSource (shader_id, 1, (const GLchar **) &source, NULL); glCompileShader (shader_id); #ifdef G_ENABLE_DEBUG if (GSK_DEBUG_CHECK (SHADERS)) { g_print ("*** Compiling %s shader from '%s' + '%s' ***\n" "%s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", shader_preamble, shader_source, source); } #endif g_free (source); glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { int log_len; char *buffer; glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len); buffer = g_malloc0 (log_len + 1); glGetShaderInfoLog (shader_id, log_len, NULL, buffer); g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_COMPILATION_FAILED, "Compilation failure in %s shader:\n%s", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", buffer); g_free (buffer); glDeleteShader (shader_id); return -1; } return shader_id; } static void gsk_shader_builder_cache_uniforms (GskShaderBuilder *builder, ShaderProgram *program) { int i; for (i = 0; i < builder->uniforms->len; i++) { const char *uniform = g_ptr_array_index (builder->uniforms, i); int loc = glGetUniformLocation (program->program_id, uniform); g_hash_table_insert (program->uniform_locations, GINT_TO_POINTER (g_quark_from_string (uniform)), GINT_TO_POINTER (loc)); } } static void gsk_shader_builder_cache_attributes (GskShaderBuilder *builder, ShaderProgram *program) { int i; for (i = 0; i < builder->attributes->len; i++) { const char *attribute = g_ptr_array_index (builder->attributes, i); int loc = glGetAttribLocation (program->program_id, attribute); g_hash_table_insert (program->attribute_locations, GINT_TO_POINTER (g_quark_from_string (attribute)), GINT_TO_POINTER (loc)); } } int gsk_shader_builder_create_program (GskShaderBuilder *builder, const char *vertex_shader, const char *fragment_shader, GError **error) { ShaderProgram *program; int vertex_id, fragment_id; int program_id; int status; g_return_val_if_fail (GSK_IS_SHADER_BUILDER (builder), -1); g_return_val_if_fail (vertex_shader != NULL, -1); g_return_val_if_fail (fragment_shader != NULL, -1); vertex_id = gsk_shader_builder_compile_shader (builder, GL_VERTEX_SHADER, builder->vertex_preamble, vertex_shader, error); if (vertex_id < 0) return -1; fragment_id = gsk_shader_builder_compile_shader (builder, GL_FRAGMENT_SHADER, builder->fragment_preamble, fragment_shader, error); if (fragment_id < 0) { glDeleteShader (vertex_id); return -1; } program_id = glCreateProgram (); glAttachShader (program_id, vertex_id); glAttachShader (program_id, fragment_id); glLinkProgram (program_id); glGetProgramiv (program_id, GL_LINK_STATUS, &status); if (status == GL_FALSE) { char *buffer = NULL; int log_len = 0; glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len); buffer = g_malloc0 (log_len + 1); glGetProgramInfoLog (program_id, log_len, NULL, buffer); g_set_error (error, GDK_GL_ERROR, GDK_GL_ERROR_LINK_FAILED, "Linking failure in shader:\n%s", buffer); g_free (buffer); glDeleteProgram (program_id); program_id = -1; goto out; } program = shader_program_new (program_id); gsk_shader_builder_cache_uniforms (builder, program); gsk_shader_builder_cache_attributes (builder, program); g_hash_table_insert (builder->programs, GINT_TO_POINTER (program_id), program); #ifdef G_ENABLE_DEBUG if (GSK_DEBUG_CHECK (SHADERS)) { GHashTableIter iter; gpointer name_p, location_p; g_hash_table_iter_init (&iter, program->uniform_locations); while (g_hash_table_iter_next (&iter, &name_p, &location_p)) { g_print ("Uniform '%s' - location: %d\n", g_quark_to_string (GPOINTER_TO_INT (name_p)), GPOINTER_TO_INT (location_p)); } g_hash_table_iter_init (&iter, program->attribute_locations); while (g_hash_table_iter_next (&iter, &name_p, &location_p)) { g_print ("Attribute '%s' - location: %d\n", g_quark_to_string (GPOINTER_TO_INT (name_p)), GPOINTER_TO_INT (location_p)); } } #endif out: if (vertex_id > 0) { glDetachShader (program_id, vertex_id); glDeleteShader (vertex_id); } if (fragment_id > 0) { glDetachShader (program_id, fragment_id); glDeleteShader (fragment_id); } return program_id; } int gsk_shader_builder_get_uniform_location (GskShaderBuilder *builder, int program_id, GQuark uniform_quark) { ShaderProgram *p = NULL; gpointer loc_p = NULL; g_return_val_if_fail (GSK_IS_SHADER_BUILDER (builder), -1); g_return_val_if_fail (program_id >= 0, -1); if (builder->uniforms->len == 0) return -1; if (!g_hash_table_lookup_extended (builder->programs, GINT_TO_POINTER (program_id), NULL, (gpointer *) &p)) return -1; if (g_hash_table_lookup_extended (p->uniform_locations, GINT_TO_POINTER (uniform_quark), NULL, &loc_p)) return GPOINTER_TO_INT (loc_p); return -1; } int gsk_shader_builder_get_attribute_location (GskShaderBuilder *builder, int program_id, GQuark attribute_quark) { ShaderProgram *p = NULL; gpointer loc_p = NULL; g_return_val_if_fail (GSK_IS_SHADER_BUILDER (builder), -1); g_return_val_if_fail (program_id >= 0, -1); if (builder->attributes->len == 0) return -1; if (!g_hash_table_lookup_extended (builder->programs, GINT_TO_POINTER (program_id), NULL, (gpointer *) &p)) return -1; if (g_hash_table_lookup_extended (p->attribute_locations, GINT_TO_POINTER (attribute_quark), NULL, &loc_p)) return GPOINTER_TO_INT (loc_p); return -1; }