/* gskngluniformstate.c * * Copyright 2020 Christian Hergert * * 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.1 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 program. If not, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include #include "gskngluniformstateprivate.h" static const guint8 uniform_sizes[] = { 0, sizeof (Uniform1f), sizeof (Uniform2f), sizeof (Uniform3f), sizeof (Uniform4f), sizeof (Uniform1f), sizeof (Uniform2f), sizeof (Uniform3f), sizeof (Uniform4f), sizeof (Uniform1i), sizeof (Uniform2i), sizeof (Uniform3i), sizeof (Uniform4i), sizeof (Uniform1ui), sizeof (guint), sizeof (graphene_matrix_t), sizeof (GskRoundedRect), sizeof (GdkRGBA), 0, }; GskNglUniformState * gsk_ngl_uniform_state_new (void) { GskNglUniformState *state; state = g_atomic_rc_box_new0 (GskNglUniformState); state->programs = g_hash_table_new_full (NULL, NULL, NULL, g_free); state->values_len = 4096; state->values_pos = 0; state->values_buf = g_malloc (4096); memset (state->apply_hash, 0, sizeof state->apply_hash); return g_steal_pointer (&state); } GskNglUniformState * gsk_ngl_uniform_state_ref (GskNglUniformState *state) { return g_atomic_rc_box_acquire (state); } static void gsk_ngl_uniform_state_finalize (gpointer data) { GskNglUniformState *state = data; g_clear_pointer (&state->programs, g_hash_table_unref); g_clear_pointer (&state->values_buf, g_free); } void gsk_ngl_uniform_state_unref (GskNglUniformState *state) { g_atomic_rc_box_release_full (state, gsk_ngl_uniform_state_finalize); } gpointer gsk_ngl_uniform_state_init_value (GskNglUniformState *state, GskNglUniformProgram *program, GskNglUniformFormat format, guint array_count, guint key, GskNglUniformMapping **infoptr) { GskNglUniformMapping *mapping; guint offset; g_assert (state != NULL); g_assert (array_count < 32); g_assert ((int)format >= 0 && format < GSK_NGL_UNIFORM_FORMAT_LAST); g_assert (format > 0); g_assert (program != NULL); g_assert (key < program->n_mappings); mapping = &program->mappings[key]; if (mapping->location == -1) { *infoptr = NULL; return NULL; } if G_LIKELY (format == mapping->info.format) { if G_LIKELY (array_count <= mapping->info.array_count) { *infoptr = mapping; return GSK_NGL_UNIFORM_VALUE (state->values_buf, mapping->info.offset); } /* We found the uniform, but there is not enough space for the * amount that was requested. Instead, allocate new space and * set the value to "initial" so that the caller just writes * over the previous value. * * This can happen when using dynamic array lengths like the * "n_color_stops" in gradient shaders. */ goto setup_info; } else if (mapping->info.format == 0) { goto setup_info; } else { g_critical ("Attempt to access uniform with different type of value " "than it was initialized with. Program %u Location %u. " "Was %d now %d (array length %d now %d).", program->program_id, key, mapping->info.format, format, mapping->info.array_count, array_count); *infoptr = NULL; return NULL; } setup_info: gsk_ngl_uniform_state_realloc (state, uniform_sizes[format] * MAX (1, array_count), &offset); /* we have 21 bits for offset */ g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS)); mapping->info.format = format; mapping->info.offset = offset; mapping->info.array_count = array_count; mapping->info.initial = TRUE; mapping->stamp = 0; *infoptr = mapping; return GSK_NGL_UNIFORM_VALUE (state->values_buf, mapping->info.offset); } void gsk_ngl_uniform_state_end_frame (GskNglUniformState *state) { GHashTableIter iter; GskNglUniformProgram *program; guint allocator = 0; g_return_if_fail (state != NULL); /* After a frame finishes, we want to remove all our copies of uniform * data that isn't needed any longer. Since we treat it as uninitialized * after this frame (to reset it on first use next frame) we can just * discard it but keep an allocation around to reuse. */ g_hash_table_iter_init (&iter, state->programs); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&program)) { for (guint j = 0; j < program->n_mappings; j++) { GskNglUniformMapping *mapping = &program->mappings[j]; guint size; /* Skip unused uniform mappings */ if (mapping->info.format == 0 || mapping->location == -1) continue; /* Calculate how much size is needed for the uniform, including arrays */ size = uniform_sizes[mapping->info.format] * MAX (1, mapping->info.array_count); /* Adjust alignment for value */ allocator += gsk_ngl_uniform_state_align (allocator, size); /* Offset is in slots of 4 bytes */ mapping->info.offset = allocator / 4; mapping->info.initial = TRUE; mapping->stamp = 0; /* Now advance for this items data */ allocator += size; } } state->values_pos = allocator; /* It can happen that our space requirements grow due to * difference in order increasing padding. As a pragmatic * solution to this, just increase the allocation to cover * the predefined mappins. */ if (allocator > state->values_len) { while (allocator > state->values_len) state->values_len *= 2; state->values_buf = g_realloc (state->values_buf, state->values_len); } memset (state->apply_hash, 0, sizeof state->apply_hash); } gsize gsk_ngl_uniform_format_size (GskNglUniformFormat format) { g_assert (format > 0); g_assert (format < GSK_NGL_UNIFORM_FORMAT_LAST); return uniform_sizes[format]; } GskNglUniformProgram * gsk_ngl_uniform_state_get_program (GskNglUniformState *state, guint program, const GskNglUniformMapping *mappings, guint n_mappings) { GskNglUniformProgram *ret; g_return_val_if_fail (state != NULL, NULL); g_return_val_if_fail (program > 0, NULL); g_return_val_if_fail (program < G_MAXUINT, NULL); g_return_val_if_fail (n_mappings <= G_N_ELEMENTS (ret->mappings), NULL); ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program)); if (ret == NULL) { ret = g_new0 (GskNglUniformProgram, 1); ret->program_id = program; ret->n_mappings = n_mappings; memcpy (ret->mappings, mappings, n_mappings * sizeof *mappings); g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret); } return ret; }