1/* gskgluniformstate.c
2 *
3 * Copyright 2020 Christian Hergert <chergert@redhat.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: LGPL-2.1-or-later
19 */
20
21#include "config.h"
22
23#include <gsk/gskroundedrectprivate.h>
24#include <string.h>
25
26#include "gskgluniformstateprivate.h"
27
28static const guint8 uniform_sizes[] = {
29 0,
30
31 sizeof (Uniform1f),
32 sizeof (Uniform2f),
33 sizeof (Uniform3f),
34 sizeof (Uniform4f),
35
36 sizeof (Uniform1f),
37 sizeof (Uniform2f),
38 sizeof (Uniform3f),
39 sizeof (Uniform4f),
40
41 sizeof (Uniform1i),
42 sizeof (Uniform2i),
43 sizeof (Uniform3i),
44 sizeof (Uniform4i),
45
46 sizeof (Uniform1ui),
47
48 sizeof (guint),
49
50 sizeof (graphene_matrix_t),
51 sizeof (GskRoundedRect),
52 sizeof (GdkRGBA),
53
54 0,
55};
56
57GskGLUniformState *
58gsk_gl_uniform_state_new (void)
59{
60 GskGLUniformState *state;
61
62 state = g_atomic_rc_box_new0 (GskGLUniformState);
63 state->programs = g_hash_table_new_full (NULL, NULL, NULL, value_destroy_func: g_free);
64 state->values_len = 4096;
65 state->values_pos = 0;
66 state->values_buf = g_malloc (n_bytes: 4096);
67
68 memset (s: state->apply_hash, c: 0, n: sizeof state->apply_hash);
69
70 return g_steal_pointer (&state);
71}
72
73GskGLUniformState *
74gsk_gl_uniform_state_ref (GskGLUniformState *state)
75{
76 return g_atomic_rc_box_acquire (state);
77}
78
79static void
80gsk_gl_uniform_state_finalize (gpointer data)
81{
82 GskGLUniformState *state = data;
83
84 g_clear_pointer (&state->programs, g_hash_table_unref);
85 g_clear_pointer (&state->values_buf, g_free);
86}
87
88void
89gsk_gl_uniform_state_unref (GskGLUniformState *state)
90{
91 g_atomic_rc_box_release_full (mem_block: state, clear_func: gsk_gl_uniform_state_finalize);
92}
93
94gpointer
95gsk_gl_uniform_state_init_value (GskGLUniformState *state,
96 GskGLUniformProgram *program,
97 GskGLUniformFormat format,
98 guint array_count,
99 guint key,
100 GskGLUniformMapping **infoptr)
101{
102 GskGLUniformMapping *mapping;
103 guint offset;
104
105 g_assert (state != NULL);
106 g_assert (array_count < 32);
107 g_assert ((int)format >= 0 && format < GSK_GL_UNIFORM_FORMAT_LAST);
108 g_assert (format > 0);
109 g_assert (program != NULL);
110 g_assert (key < program->n_mappings);
111
112 mapping = &program->mappings[key];
113
114 if (mapping->location == -1)
115 {
116 *infoptr = NULL;
117 return NULL;
118 }
119
120 if G_LIKELY (format == mapping->info.format)
121 {
122 if G_LIKELY (array_count <= mapping->info.array_count)
123 {
124 *infoptr = mapping;
125 return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
126 }
127
128 /* We found the uniform, but there is not enough space for the
129 * amount that was requested. Instead, allocate new space and
130 * set the value to "initial" so that the caller just writes
131 * over the previous value.
132 *
133 * This can happen when using dynamic array lengths like the
134 * "n_color_stops" in gradient shaders.
135 */
136 goto setup_info;
137 }
138 else if (mapping->info.format == 0)
139 {
140 goto setup_info;
141 }
142 else
143 {
144 g_critical ("Attempt to access uniform with different type of value "
145 "than it was initialized with. Program %u Location %u. "
146 "Was %d now %d (array length %d now %d).",
147 program->program_id, key, mapping->info.format, format,
148 mapping->info.array_count, array_count);
149 *infoptr = NULL;
150 return NULL;
151 }
152
153setup_info:
154
155 gsk_gl_uniform_state_realloc (state,
156 size: uniform_sizes[format] * MAX (1, array_count),
157 offset: &offset);
158
159 /* we have 21 bits for offset */
160 g_assert (offset < (1 << GSK_GL_UNIFORM_OFFSET_BITS));
161
162 mapping->info.format = format;
163 mapping->info.offset = offset;
164 mapping->info.array_count = array_count;
165 mapping->info.initial = TRUE;
166 mapping->stamp = 0;
167
168 *infoptr = mapping;
169
170 return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
171}
172
173void
174gsk_gl_uniform_state_end_frame (GskGLUniformState *state)
175{
176 GHashTableIter iter;
177 GskGLUniformProgram *program;
178 guint allocator = 0;
179
180 g_return_if_fail (state != NULL);
181
182 /* After a frame finishes, we want to remove all our copies of uniform
183 * data that isn't needed any longer. Since we treat it as uninitialized
184 * after this frame (to reset it on first use next frame) we can just
185 * discard it but keep an allocation around to reuse.
186 */
187
188 g_hash_table_iter_init (iter: &iter, hash_table: state->programs);
189 while (g_hash_table_iter_next (iter: &iter, NULL, value: (gpointer *)&program))
190 {
191 for (guint j = 0; j < program->n_mappings; j++)
192 {
193 GskGLUniformMapping *mapping = &program->mappings[j];
194 guint size;
195
196 /* Skip unused uniform mappings */
197 if (mapping->info.format == 0 || mapping->location == -1)
198 continue;
199
200 /* Calculate how much size is needed for the uniform, including arrays */
201 size = uniform_sizes[mapping->info.format] * MAX (1, mapping->info.array_count);
202
203 /* Adjust alignment for value */
204 allocator += gsk_gl_uniform_state_align (current_pos: allocator, size);
205
206 /* Offset is in slots of 4 bytes */
207 mapping->info.offset = allocator / 4;
208 mapping->info.initial = TRUE;
209 mapping->stamp = 0;
210
211 /* Now advance for this items data */
212 allocator += size;
213 }
214 }
215
216 state->values_pos = allocator;
217
218 /* It can happen that our space requirements grow due to
219 * difference in order increasing padding. As a pragmatic
220 * solution to this, just increase the allocation to cover
221 * the predefined mappins.
222 */
223 if (allocator > state->values_len)
224 {
225 while (allocator > state->values_len)
226 state->values_len *= 2;
227 state->values_buf = g_realloc (mem: state->values_buf, n_bytes: state->values_len);
228 }
229
230 memset (s: state->apply_hash, c: 0, n: sizeof state->apply_hash);
231}
232
233gsize
234gsk_gl_uniform_format_size (GskGLUniformFormat format)
235{
236 g_assert (format > 0);
237 g_assert (format < GSK_GL_UNIFORM_FORMAT_LAST);
238
239 return uniform_sizes[format];
240}
241
242GskGLUniformProgram *
243gsk_gl_uniform_state_get_program (GskGLUniformState *state,
244 guint program,
245 const GskGLUniformMapping *mappings,
246 guint n_mappings)
247{
248 GskGLUniformProgram *ret;
249
250 g_return_val_if_fail (state != NULL, NULL);
251 g_return_val_if_fail (program > 0, NULL);
252 g_return_val_if_fail (program < G_MAXUINT, NULL);
253 g_return_val_if_fail (n_mappings <= G_N_ELEMENTS (ret->mappings), NULL);
254
255 ret = g_hash_table_lookup (hash_table: state->programs, GUINT_TO_POINTER (program));
256
257 if (ret == NULL)
258 {
259 ret = g_new0 (GskGLUniformProgram, 1);
260 ret->program_id = program;
261 ret->n_mappings = n_mappings;
262
263 memcpy (dest: ret->mappings, src: mappings, n: n_mappings * sizeof *mappings);
264
265 g_hash_table_insert (hash_table: state->programs, GUINT_TO_POINTER (program), value: ret);
266 }
267
268 return ret;
269}
270

source code of gtk/gsk/gl/gskgluniformstate.c