1/* gdkgltexture.c
2 *
3 * Copyright 2016 Benjamin Otte
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 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 library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gdkgltextureprivate.h"
22
23#include "gdkdisplayprivate.h"
24#include "gdkglcontextprivate.h"
25#include "gdkmemoryformatprivate.h"
26#include "gdkmemorytextureprivate.h"
27#include "gdktextureprivate.h"
28
29#include <epoxy/gl.h>
30
31/**
32 * GdkGLTexture:
33 *
34 * A GdkTexture representing a GL texture object.
35 */
36
37struct _GdkGLTexture {
38 GdkTexture parent_instance;
39
40 GdkGLContext *context;
41 guint id;
42
43 GdkTexture *saved;
44
45 GDestroyNotify destroy;
46 gpointer data;
47};
48
49struct _GdkGLTextureClass {
50 GdkTextureClass parent_class;
51};
52
53G_DEFINE_TYPE (GdkGLTexture, gdk_gl_texture, GDK_TYPE_TEXTURE)
54
55static void
56gdk_gl_texture_dispose (GObject *object)
57{
58 GdkGLTexture *self = GDK_GL_TEXTURE (object);
59
60 if (self->destroy)
61 {
62 self->destroy (self->data);
63 self->destroy = NULL;
64 self->data = NULL;
65 }
66
67 g_clear_object (&self->context);
68 self->id = 0;
69
70 g_clear_object (&self->saved);
71
72 G_OBJECT_CLASS (gdk_gl_texture_parent_class)->dispose (object);
73}
74
75typedef struct _InvokeData
76{
77 GdkGLTexture *self;
78 volatile int spinlock;
79 GFunc func;
80 gpointer data;
81} InvokeData;
82
83static gboolean
84gdk_gl_texture_invoke_callback (gpointer data)
85{
86 InvokeData *invoke = data;
87 GdkGLContext *context;
88
89 context = gdk_display_get_gl_context (display: gdk_gl_context_get_display (context: invoke->self->context));
90
91 gdk_gl_context_make_current (context);
92 glBindTexture (GL_TEXTURE_2D, invoke->self->id);
93
94 invoke->func (invoke->self, invoke->data);
95
96 g_atomic_int_set (&invoke->spinlock, 1);
97
98 return FALSE;
99}
100
101static void
102gdk_gl_texture_run (GdkGLTexture *self,
103 GFunc func,
104 gpointer data)
105{
106 InvokeData invoke = { self, 0, func, data };
107
108 g_main_context_invoke (NULL, function: gdk_gl_texture_invoke_callback, data: &invoke);
109
110 while (g_atomic_int_get (&invoke.spinlock) == 0);
111}
112
113typedef struct _Download Download;
114
115struct _Download
116{
117 GdkMemoryFormat format;
118 guchar *data;
119 gsize stride;
120};
121
122static gboolean
123gdk_gl_texture_find_format (gboolean use_es,
124 GLint gl_format,
125 GLint gl_type,
126 GdkMemoryFormat *out_format)
127{
128 GdkMemoryFormat format;
129
130 for (format = 0; format < GDK_MEMORY_N_FORMATS; format++)
131 {
132 GLenum q_internal_format, q_format, q_type;
133
134 if (!gdk_memory_format_gl_format (format, gles: use_es, out_internal_format: &q_internal_format, out_format: &q_format, out_type: &q_type))
135 continue;
136
137 if (q_format != gl_format || q_type != gl_type)
138 continue;
139
140 *out_format = format;
141 return TRUE;
142 }
143
144 return FALSE;
145}
146
147static inline void
148gdk_gl_texture_do_download (gpointer texture_,
149 gpointer download_)
150{
151 gsize expected_stride;
152 GdkGLTexture *self = texture_;
153 GdkTexture *texture = texture_;
154 Download *download = download_;
155 GLenum gl_internal_format, gl_format, gl_type;
156
157 expected_stride = texture->width * gdk_memory_format_bytes_per_pixel (format: download->format);
158
159 if (download->stride == expected_stride &&
160 !gdk_gl_context_get_use_es (context: self->context) &&
161 gdk_memory_format_gl_format (format: download->format, TRUE, out_internal_format: &gl_internal_format, out_format: &gl_format, out_type: &gl_type))
162 {
163 glGetTexImage (GL_TEXTURE_2D,
164 0,
165 gl_format,
166 gl_type,
167 download->data);
168 }
169 else
170 {
171 GdkMemoryFormat actual_format;
172 GLint gl_read_format, gl_read_type;
173 GLuint fbo;
174
175 glGenFramebuffers (1, &fbo);
176 glBindFramebuffer (GL_FRAMEBUFFER, fbo);
177 glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->id, 0);
178 if (gdk_gl_context_check_version (context: self->context, required_gl_major: 4, required_gl_minor: 3, required_gles_major: 3, required_gles_minor: 1))
179 {
180 glGetFramebufferParameteriv (GL_FRAMEBUFFER, GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_read_format);
181 glGetFramebufferParameteriv (GL_FRAMEBUFFER, GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_read_type);
182 if (!gdk_gl_texture_find_format (use_es: gdk_gl_context_get_use_es (context: self->context), gl_format: gl_read_format, gl_type: gl_read_type, out_format: &actual_format))
183 actual_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; /* pray */
184 }
185 else
186 {
187 gl_read_format = GL_RGBA;
188 gl_read_type = GL_UNSIGNED_BYTE;
189 actual_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
190 }
191
192 if (download->format == actual_format &&
193 (download->stride == expected_stride))
194 {
195 glReadPixels (0, 0,
196 texture->width, texture->height,
197 gl_read_format,
198 gl_read_type,
199 download->data);
200 }
201 else
202 {
203 gsize actual_bpp = gdk_memory_format_bytes_per_pixel (format: actual_format);
204 guchar *pixels = g_malloc_n (n_blocks: texture->width * actual_bpp, n_block_bytes: texture->height);
205
206 glReadPixels (0, 0,
207 texture->width, texture->height,
208 gl_read_format,
209 gl_read_type,
210 pixels);
211
212 gdk_memory_convert (dest_data: download->data,
213 dest_stride: download->stride,
214 dest_format: download->format,
215 src_data: pixels,
216 src_stride: texture->width * actual_bpp,
217 src_format: actual_format,
218 width: texture->width,
219 height: texture->height);
220
221 g_free (mem: pixels);
222 }
223 glBindFramebuffer (GL_FRAMEBUFFER, 0);
224 glDeleteFramebuffers (1, &fbo);
225 }
226}
227
228static void
229gdk_gl_texture_download (GdkTexture *texture,
230 GdkMemoryFormat format,
231 guchar *data,
232 gsize stride)
233{
234 GdkGLTexture *self = GDK_GL_TEXTURE (texture);
235 Download download;
236
237 if (self->saved)
238 {
239 gdk_texture_do_download (texture: self->saved, format, data, stride);
240 return;
241 }
242
243 download.format = format;
244 download.data = data;
245 download.stride = stride;
246
247 gdk_gl_texture_run (self, func: gdk_gl_texture_do_download, data: &download);
248}
249
250static void
251gdk_gl_texture_class_init (GdkGLTextureClass *klass)
252{
253 GdkTextureClass *texture_class = GDK_TEXTURE_CLASS (klass);
254 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
255
256 texture_class->download = gdk_gl_texture_download;
257
258 gobject_class->dispose = gdk_gl_texture_dispose;
259}
260
261static void
262gdk_gl_texture_init (GdkGLTexture *self)
263{
264}
265
266GdkGLContext *
267gdk_gl_texture_get_context (GdkGLTexture *self)
268{
269 return self->context;
270}
271
272guint
273gdk_gl_texture_get_id (GdkGLTexture *self)
274{
275 return self->id;
276}
277
278/**
279 * gdk_gl_texture_release:
280 * @self: a `GdkTexture` wrapping a GL texture
281 *
282 * Releases the GL resources held by a `GdkGLTexture`.
283 *
284 * The texture contents are still available via the
285 * [method@Gdk.Texture.download] function, after this
286 * function has been called.
287 */
288void
289gdk_gl_texture_release (GdkGLTexture *self)
290{
291 GdkTexture *texture;
292
293 g_return_if_fail (GDK_IS_GL_TEXTURE (self));
294 g_return_if_fail (self->saved == NULL);
295
296 texture = GDK_TEXTURE (self);
297 self->saved = GDK_TEXTURE (gdk_memory_texture_from_texture (texture,
298 gdk_texture_get_format (texture)));
299
300 if (self->destroy)
301 {
302 self->destroy (self->data);
303 self->destroy = NULL;
304 self->data = NULL;
305 }
306
307 g_clear_object (&self->context);
308 self->id = 0;
309}
310
311static void
312gdk_gl_texture_determine_format (GdkGLTexture *self)
313{
314 GdkTexture *texture = GDK_TEXTURE (self);
315 GLint active_texture;
316 GLint internal_format;
317
318 /* Abort if somebody else is GL-ing here... */
319 if (self->context != gdk_gl_context_get_current () ||
320 /* ... or glGetTexLevelParameter() isn't supported */
321 !gdk_gl_context_check_version (context: self->context, required_gl_major: 0, required_gl_minor: 0, required_gles_major: 3, required_gles_minor: 1))
322 {
323 texture->format = GDK_MEMORY_DEFAULT;
324 return;
325 }
326
327 /* We need to be careful about modifying the GL context, as this is not
328 * expected during construction */
329 glGetIntegerv (GL_TEXTURE_BINDING_2D, &active_texture);
330 glBindTexture (GL_TEXTURE_2D, self->id);
331
332 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
333
334 switch (internal_format)
335 {
336 case GL_RGB8:
337 texture->format = GDK_MEMORY_R8G8B8;
338 break;
339
340 case GL_RGBA8:
341 texture->format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
342 break;
343
344 case GL_RGB16:
345 texture->format = GDK_MEMORY_R16G16B16;
346 break;
347
348 case GL_RGBA16:
349 texture->format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
350 break;
351
352 case GL_RGB16F:
353 texture->format = GDK_MEMORY_R16G16B16_FLOAT;
354 break;
355
356 case GL_RGBA16F:
357 texture->format = GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED;
358 break;
359
360 case GL_RGB32F:
361 texture->format = GDK_MEMORY_R32G32B32_FLOAT;
362 break;
363
364 case GL_RGBA32F:
365 texture->format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED;
366 break;
367
368 case GL_RGBA:
369 {
370 GLint red_size = 0;
371 GLint green_size = 0;
372 GLint blue_size = 0;
373 GLint alpha_size = 0;
374 GLint red_type = 0;
375 GLint green_type = 0;
376 GLint blue_type = 0;
377 GLint alpha_type = 0;
378
379 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_RED_TYPE, &red_type);
380 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_GREEN_TYPE, &green_type);
381 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_BLUE_TYPE, &blue_type);
382 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_TYPE, &alpha_type);
383
384 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_RED_SIZE, &red_size);
385 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_GREEN_SIZE, &green_size);
386 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_BLUE_SIZE, &blue_size);
387 glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &alpha_size);
388
389#define CHECK_RGBA(rt,gt,bt,at,rs,gs,bs,as) \
390 (red_type == rt && green_type == gt && blue_type == bt && alpha_type == at && \
391 red_size == rs && green_size == gs && blue_size == bs && alpha_size == as)
392
393 if (CHECK_RGBA (GL_UNSIGNED_NORMALIZED, GL_UNSIGNED_NORMALIZED, GL_UNSIGNED_NORMALIZED, GL_UNSIGNED_NORMALIZED, 8, 8, 8, 8))
394 {
395 texture->format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
396 break;
397 }
398
399#undef CHECK_RGBA
400 }
401
402 G_GNUC_FALLTHROUGH;
403
404 default:
405 g_warning ("Texture in unexpected format 0x%X (%d). File a bug about adding it to GTK", internal_format, internal_format);
406 /* fallback to the dumbest possible format
407 * so that even age old GLES can do it */
408 texture->format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
409 break;
410 }
411
412 glBindTexture (GL_TEXTURE_2D, active_texture);
413}
414
415/**
416 * gdk_gl_texture_new:
417 * @context: a `GdkGLContext`
418 * @id: the ID of a texture that was created with @context
419 * @width: the nominal width of the texture
420 * @height: the nominal height of the texture
421 * @destroy: a destroy notify that will be called when the GL resources
422 * are released
423 * @data: data that gets passed to @destroy
424 *
425 * Creates a new texture for an existing GL texture.
426 *
427 * Note that the GL texture must not be modified until @destroy is called,
428 * which will happen when the GdkTexture object is finalized, or due to
429 * an explicit call of [method@Gdk.GLTexture.release].
430 *
431 * Return value: (transfer full) (type GdkGLTexture): A newly-created
432 * `GdkTexture`
433 */
434GdkTexture *
435gdk_gl_texture_new (GdkGLContext *context,
436 guint id,
437 int width,
438 int height,
439 GDestroyNotify destroy,
440 gpointer data)
441{
442 GdkGLTexture *self;
443
444 g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
445 g_return_val_if_fail (id != 0, NULL);
446 g_return_val_if_fail (width > 0, NULL);
447 g_return_val_if_fail (height > 0, NULL);
448
449 self = g_object_new (GDK_TYPE_GL_TEXTURE,
450 first_property_name: "width", width,
451 "height", height,
452 NULL);
453
454 self->context = g_object_ref (context);
455 self->id = id;
456 self->destroy = destroy;
457 self->data = data;
458
459 gdk_gl_texture_determine_format (self);
460
461 return GDK_TEXTURE (self);
462}
463
464

source code of gtk/gdk/gdkgltexture.c