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 | |
37 | struct _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 | |
49 | struct _GdkGLTextureClass { |
50 | GdkTextureClass parent_class; |
51 | }; |
52 | |
53 | G_DEFINE_TYPE (GdkGLTexture, gdk_gl_texture, GDK_TYPE_TEXTURE) |
54 | |
55 | static void |
56 | gdk_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 | |
75 | typedef struct _InvokeData |
76 | { |
77 | GdkGLTexture *self; |
78 | volatile int spinlock; |
79 | GFunc func; |
80 | gpointer data; |
81 | } InvokeData; |
82 | |
83 | static gboolean |
84 | gdk_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 | |
101 | static void |
102 | gdk_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 | |
113 | typedef struct _Download Download; |
114 | |
115 | struct _Download |
116 | { |
117 | GdkMemoryFormat format; |
118 | guchar *data; |
119 | gsize stride; |
120 | }; |
121 | |
122 | static gboolean |
123 | gdk_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 | |
147 | static inline void |
148 | gdk_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 | |
228 | static void |
229 | gdk_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 | |
250 | static void |
251 | gdk_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 | |
261 | static void |
262 | gdk_gl_texture_init (GdkGLTexture *self) |
263 | { |
264 | } |
265 | |
266 | GdkGLContext * |
267 | gdk_gl_texture_get_context (GdkGLTexture *self) |
268 | { |
269 | return self->context; |
270 | } |
271 | |
272 | guint |
273 | gdk_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 | */ |
288 | void |
289 | gdk_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 | |
311 | static void |
312 | gdk_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 | */ |
434 | GdkTexture * |
435 | gdk_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 | |