/* gdkgltexture.c * * Copyright 2016 Benjamin Otte * * 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 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 library. If not, see . */ #include "config.h" #include "gdkgltextureprivate.h" #include "gdkdisplayprivate.h" #include "gdkmemorytextureprivate.h" #include "gdktextureprivate.h" #include /** * GdkGLTexture: * * A GdkTexture representing a GL texture object. */ struct _GdkGLTexture { GdkTexture parent_instance; GdkGLContext *context; guint id; GdkTexture *saved; GDestroyNotify destroy; gpointer data; }; struct _GdkGLTextureClass { GdkTextureClass parent_class; }; G_DEFINE_TYPE (GdkGLTexture, gdk_gl_texture, GDK_TYPE_TEXTURE) static void gdk_gl_texture_dispose (GObject *object) { GdkGLTexture *self = GDK_GL_TEXTURE (object); if (self->destroy) { self->destroy (self->data); self->destroy = NULL; self->data = NULL; } g_clear_object (&self->context); self->id = 0; g_clear_object (&self->saved); G_OBJECT_CLASS (gdk_gl_texture_parent_class)->dispose (object); } typedef struct _InvokeData { GdkGLTexture *self; volatile int spinlock; GFunc func; gpointer data; } InvokeData; static gboolean gdk_gl_texture_invoke_callback (gpointer data) { InvokeData *invoke = data; GdkGLContext *context; context = gdk_display_get_gl_context (gdk_gl_context_get_display (invoke->self->context)); gdk_gl_context_make_current (context); glBindTexture (GL_TEXTURE_2D, invoke->self->id); invoke->func (invoke->self, invoke->data); g_atomic_int_set (&invoke->spinlock, 1); return FALSE; } static void gdk_gl_texture_run (GdkGLTexture *self, GFunc func, gpointer data) { InvokeData invoke = { self, 0, func, data }; g_main_context_invoke (NULL, gdk_gl_texture_invoke_callback, &invoke); while (g_atomic_int_get (&invoke.spinlock) == 0); } static inline void gdk_gl_texture_get_tex_image (GdkGLTexture *self, GLenum gl_format, GLenum gl_type, GLvoid *data) { if (gdk_gl_context_get_use_es (self->context)) { GdkTexture *texture = GDK_TEXTURE (self); GLuint fbo; glGenFramebuffers (1, &fbo); glBindFramebuffer (GL_FRAMEBUFFER, fbo); glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->id, 0); glReadPixels (0, 0, texture->width, texture->height, gl_format, gl_type, data); glBindFramebuffer (GL_FRAMEBUFFER, 0); glDeleteFramebuffers (1, &fbo); } else { glGetTexImage (GL_TEXTURE_2D, 0, gl_format, gl_type, data); } } static void gdk_gl_texture_do_download_texture (gpointer texture_, gpointer result_) { GdkTexture *texture = texture_; GdkTexture **result = result_; GdkMemoryFormat format; GLint internal_format, gl_format, gl_type; guchar *data; gsize stride; GBytes *bytes; glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format); switch (internal_format) { case GL_RGB8: format = GDK_MEMORY_R8G8B8; gl_format = GL_RGB; gl_type = GL_UNSIGNED_BYTE; break; case GL_RGBA8: format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; gl_format = GL_RGBA; gl_type = GL_UNSIGNED_BYTE; break; case GL_RGB16: format = GDK_MEMORY_R16G16B16; gl_format = GL_RGB; gl_type = GL_UNSIGNED_SHORT; break; case GL_RGBA16: format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED; gl_format = GL_RGBA; gl_type = GL_UNSIGNED_SHORT; break; case GL_RGB16F: format = GDK_MEMORY_R16G16B16_FLOAT; gl_format = GL_RGB; gl_type = GL_HALF_FLOAT; break; case GL_RGBA16F: format = GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED; gl_format = GL_RGBA; gl_type = GL_HALF_FLOAT; break; case GL_RGB32F: format = GDK_MEMORY_R32G32B32_FLOAT; gl_format = GL_RGB; gl_type = GL_FLOAT; break; case GL_RGBA32F: format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED; gl_format = GL_RGBA; gl_type = GL_FLOAT; break; default: g_warning ("Texture in unexpected format 0x%X (%d). File a bug about adding it to GTK", internal_format, internal_format); /* fallback to the dumbest possible format * so that even age old GLES can do it */ format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; gl_format = GL_RGBA; gl_type = GL_UNSIGNED_BYTE; break; } stride = gdk_memory_format_bytes_per_pixel (format) * texture->width; data = g_malloc (stride * texture->height); gdk_gl_texture_get_tex_image (texture_, gl_format, gl_type, data); bytes = g_bytes_new_take (data, stride * texture->height); *result = gdk_memory_texture_new (texture->width, texture->height, format, bytes, stride); g_bytes_unref (bytes); } static GdkTexture * gdk_gl_texture_download_texture (GdkTexture *texture) { GdkGLTexture *self = GDK_GL_TEXTURE (texture); GdkTexture *result; if (self->saved) return g_object_ref (self->saved); gdk_gl_texture_run (self, gdk_gl_texture_do_download_texture, &result); return result; } static void gdk_gl_texture_do_download (gpointer texture, gpointer data) { glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, #if G_BYTE_ORDER == G_LITTLE_ENDIAN GL_UNSIGNED_INT_8_8_8_8_REV, #elif G_BYTE_ORDER == G_BIG_ENDIAN GL_UNSIGNED_BYTE, #else #error "Unknown byte order for gdk_gl_texture_download()" #endif data); } static void gdk_gl_texture_download (GdkTexture *texture, guchar *data, gsize stride) { GdkGLTexture *self = GDK_GL_TEXTURE (texture); if (self->saved) { gdk_texture_download (self->saved, data, stride); return; } if (gdk_gl_context_get_use_es (self->context) || stride != texture->width * 4) { GDK_TEXTURE_CLASS (gdk_gl_texture_parent_class)->download (texture, data, stride); return; } gdk_gl_texture_run (self, gdk_gl_texture_do_download, data); } static void gdk_gl_texture_do_download_float (gpointer texture, gpointer data) { glGetTexImage (GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, data); } static void gdk_gl_texture_download_float (GdkTexture *texture, float *data, gsize stride) { GdkGLTexture *self = GDK_GL_TEXTURE (texture); if (self->saved) { gdk_texture_download_float (self->saved, data, stride); return; } if (gdk_gl_context_get_use_es (self->context) || stride != texture->width * 4) { GDK_TEXTURE_CLASS (gdk_gl_texture_parent_class)->download_float (texture, data, stride); return; } gdk_gl_texture_run (self, gdk_gl_texture_do_download_float, data); } static void gdk_gl_texture_class_init (GdkGLTextureClass *klass) { GdkTextureClass *texture_class = GDK_TEXTURE_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); texture_class->download_texture = gdk_gl_texture_download_texture; texture_class->download = gdk_gl_texture_download; texture_class->download_float = gdk_gl_texture_download_float; gobject_class->dispose = gdk_gl_texture_dispose; } static void gdk_gl_texture_init (GdkGLTexture *self) { } GdkGLContext * gdk_gl_texture_get_context (GdkGLTexture *self) { return self->context; } guint gdk_gl_texture_get_id (GdkGLTexture *self) { return self->id; } /** * gdk_gl_texture_release: * @self: a `GdkTexture` wrapping a GL texture * * Releases the GL resources held by a `GdkGLTexture`. * * The texture contents are still available via the * [method@Gdk.Texture.download] function, after this * function has been called. */ void gdk_gl_texture_release (GdkGLTexture *self) { g_return_if_fail (GDK_IS_GL_TEXTURE (self)); g_return_if_fail (self->saved == NULL); self->saved = gdk_texture_download_texture (GDK_TEXTURE (self)); if (self->destroy) { self->destroy (self->data); self->destroy = NULL; self->data = NULL; } g_clear_object (&self->context); self->id = 0; } /** * gdk_gl_texture_new: * @context: a `GdkGLContext` * @id: the ID of a texture that was created with @context * @width: the nominal width of the texture * @height: the nominal height of the texture * @destroy: a destroy notify that will be called when the GL resources * are released * @data: data that gets passed to @destroy * * Creates a new texture for an existing GL texture. * * Note that the GL texture must not be modified until @destroy is called, * which will happen when the GdkTexture object is finalized, or due to * an explicit call of [method@Gdk.GLTexture.release]. * * Return value: (transfer full): A newly-created `GdkTexture` */ GdkTexture * gdk_gl_texture_new (GdkGLContext *context, guint id, int width, int height, GDestroyNotify destroy, gpointer data) { GdkGLTexture *self; g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); g_return_val_if_fail (id != 0, NULL); g_return_val_if_fail (width > 0, NULL); g_return_val_if_fail (height > 0, NULL); self = g_object_new (GDK_TYPE_GL_TEXTURE, "width", width, "height", height, NULL); self->context = g_object_ref (context); self->id = id; self->destroy = destroy; self->data = data; return GDK_TEXTURE (self); }