forked from AuroraMiddleware/gtk
551f76ea69
GLES only allows downloading float if the texture matches specific criteria and I'm too lazy to determine them, so always fall back. And the custom stride fallback code isn't necessary, because falling back does exactly that step already.
426 lines
11 KiB
C
426 lines
11 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gdkgltextureprivate.h"
|
|
|
|
#include "gdkdisplayprivate.h"
|
|
#include "gdkmemorytextureprivate.h"
|
|
#include "gdktextureprivate.h"
|
|
|
|
#include <epoxy/gl.h>
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|