gtk2/gsk/gskgldriver.c
Benjamin Otte 48e7f4191f gsktexture: Allow attaching render data to textures
This allows renderers (or anyone really) to attach "render data" to
textures. Only the first render data sticks.

You can gsk_texture_set_render_data() with the key you will use to
look the data up again, and if no data has been set yet, yours will be
set.

You can retrieve this data via gsk_texture_get_render_data() later on.
If your data has been cleared, NULL will be returned.

When gsk_texture_clear_render_data() is called (which the texture will
call when it is finalized), your destory notify will be called and you
have to release your render data.

The GL driver uses this to attach texture ids to GskTextures.
2016-11-16 17:36:33 +01:00

851 lines
21 KiB
C

#include "config.h"
#include "gskgldriverprivate.h"
#include "gskdebugprivate.h"
#include "gsktextureprivate.h"
#include <gdk/gdk.h>
#include <epoxy/gl.h>
typedef struct {
GLuint texture_id;
int width;
int height;
GLuint min_filter;
GLuint mag_filter;
GArray *fbos;
GskTexture *user;
gboolean in_use : 1;
} Texture;
typedef struct {
GLuint vao_id;
GLuint buffer_id;
GLuint position_id;
GLuint uv_id;
GskQuadVertex *quads;
int n_quads;
gboolean in_use : 1;
} Vao;
typedef struct {
GLuint fbo_id;
GLuint depth_stencil_id;
} Fbo;
struct _GskGLDriver
{
GObject parent_instance;
GdkGLContext *gl_context;
Fbo default_fbo;
GHashTable *textures;
GHashTable *vaos;
Texture *bound_source_texture;
Texture *bound_mask_texture;
Vao *bound_vao;
Fbo *bound_fbo;
int max_texture_size;
gboolean in_frame : 1;
};
enum
{
PROP_GL_CONTEXT = 1,
N_PROPS
};
static GParamSpec *gsk_gl_driver_properties[N_PROPS];
G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
static Texture *
texture_new (void)
{
return g_slice_new0 (Texture);
}
static void
texture_free (gpointer data)
{
Texture *t = data;
if (t->user)
gsk_texture_clear_render_data (t->user);
g_clear_pointer (&t->fbos, g_array_unref);
glDeleteTextures (1, &t->texture_id);
g_slice_free (Texture, t);
}
static void
fbo_clear (gpointer data)
{
Fbo *f = data;
if (f->depth_stencil_id != 0)
glDeleteRenderbuffers (1, &f->depth_stencil_id);
glDeleteFramebuffers (1, &f->fbo_id);
}
static Vao *
vao_new (void)
{
return g_slice_new0 (Vao);
}
static void
vao_free (gpointer data)
{
Vao *v = data;
g_free (v->quads);
glDeleteBuffers (1, &v->buffer_id);
glDeleteVertexArrays (1, &v->vao_id);
g_slice_free (Vao, v);
}
static void
gsk_gl_driver_finalize (GObject *gobject)
{
GskGLDriver *self = GSK_GL_DRIVER (gobject);
gdk_gl_context_make_current (self->gl_context);
g_clear_pointer (&self->textures, g_hash_table_unref);
g_clear_pointer (&self->vaos, g_hash_table_unref);
if (self->gl_context == gdk_gl_context_get_current ())
gdk_gl_context_clear_current ();
g_clear_object (&self->gl_context);
G_OBJECT_CLASS (gsk_gl_driver_parent_class)->finalize (gobject);
}
static void
gsk_gl_driver_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GskGLDriver *self = GSK_GL_DRIVER (gobject);
switch (prop_id)
{
case PROP_GL_CONTEXT:
self->gl_context = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gsk_gl_driver_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GskGLDriver *self = GSK_GL_DRIVER (gobject);
switch (prop_id)
{
case PROP_GL_CONTEXT:
g_value_set_object (value, self->gl_context);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gsk_gl_driver_class_init (GskGLDriverClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gsk_gl_driver_set_property;
gobject_class->get_property = gsk_gl_driver_get_property;
gobject_class->finalize = gsk_gl_driver_finalize;
gsk_gl_driver_properties[PROP_GL_CONTEXT] =
g_param_spec_object ("gl-context", "GL Context", "The GL context used by the driver",
GDK_TYPE_GL_CONTEXT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, gsk_gl_driver_properties);
}
static void
gsk_gl_driver_init (GskGLDriver *self)
{
self->textures = g_hash_table_new_full (NULL, NULL, NULL, texture_free);
self->vaos = g_hash_table_new_full (NULL, NULL, NULL, vao_free);
self->max_texture_size = -1;
}
GskGLDriver *
gsk_gl_driver_new (GdkGLContext *context)
{
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
return g_object_new (GSK_TYPE_GL_DRIVER,
"gl-context", context,
NULL);
}
void
gsk_gl_driver_begin_frame (GskGLDriver *driver)
{
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_return_if_fail (!driver->in_frame);
driver->in_frame = TRUE;
if (driver->max_texture_size < 0)
{
glGetIntegerv (GL_MAX_TEXTURE_SIZE, (GLint *) &driver->max_texture_size);
GSK_NOTE (OPENGL, g_print ("GL max texture size: %d\n", driver->max_texture_size));
}
glGetIntegerv (GL_FRAMEBUFFER_BINDING, (GLint *) &(driver->default_fbo.fbo_id));
driver->bound_fbo = &driver->default_fbo;
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, 0);
glActiveTexture (GL_TEXTURE0 + 1);
glBindTexture (GL_TEXTURE_2D, 0);
glBindVertexArray (0);
glUseProgram (0);
glActiveTexture (GL_TEXTURE0);
}
void
gsk_gl_driver_end_frame (GskGLDriver *driver)
{
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_return_if_fail (driver->in_frame);
glBindTexture (GL_TEXTURE_2D, 0);
glUseProgram (0);
glBindVertexArray (0);
driver->bound_source_texture = NULL;
driver->bound_mask_texture = NULL;
driver->bound_vao = NULL;
driver->bound_fbo = NULL;
GSK_NOTE (OPENGL,
g_print ("*** Frame end: textures=%d, vaos=%d\n",
g_hash_table_size (driver->textures),
g_hash_table_size (driver->vaos)));
driver->in_frame = FALSE;
}
int
gsk_gl_driver_collect_textures (GskGLDriver *driver)
{
GHashTableIter iter;
gpointer value_p = NULL;
int old_size;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
g_return_val_if_fail (!driver->in_frame, 0);
old_size = g_hash_table_size (driver->textures);
g_hash_table_iter_init (&iter, driver->textures);
while (g_hash_table_iter_next (&iter, NULL, &value_p))
{
Texture *t = value_p;
if (t->user)
continue;
if (t->in_use)
{
t->in_use = FALSE;
g_clear_pointer (&t->fbos, g_array_unref);
}
else
g_hash_table_iter_remove (&iter);
}
return old_size - g_hash_table_size (driver->textures);
}
int
gsk_gl_driver_collect_vaos (GskGLDriver *driver)
{
GHashTableIter iter;
gpointer value_p = NULL;
int old_size;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), 0);
g_return_val_if_fail (!driver->in_frame, 0);
old_size = g_hash_table_size (driver->vaos);
g_hash_table_iter_init (&iter, driver->vaos);
while (g_hash_table_iter_next (&iter, NULL, &value_p))
{
Vao *v = value_p;
if (v->in_use)
v->in_use = FALSE;
else
g_hash_table_iter_remove (&iter);
}
return old_size - g_hash_table_size (driver->vaos);
}
int
gsk_gl_driver_get_max_texture_size (GskGLDriver *driver)
{
if (driver->max_texture_size < 0)
{
if (gdk_gl_context_get_use_es (driver->gl_context))
return 2048;
return 1024;
}
return driver->max_texture_size;
}
static Texture *
gsk_gl_driver_get_texture (GskGLDriver *driver,
int texture_id)
{
Texture *t;
if (g_hash_table_lookup_extended (driver->textures, GINT_TO_POINTER (texture_id), NULL, (gpointer *) &t))
return t;
return NULL;
}
static Vao *
gsk_gl_driver_get_vao (GskGLDriver *driver,
int vao_id)
{
Vao *v;
if (g_hash_table_lookup_extended (driver->vaos, GINT_TO_POINTER (vao_id), NULL, (gpointer *) &v))
return v;
return NULL;
}
static Fbo *
gsk_gl_driver_get_fbo (GskGLDriver *driver,
int texture_id)
{
Texture *t = gsk_gl_driver_get_texture (driver, texture_id);
if (t->fbos == NULL)
return &driver->default_fbo;
return &g_array_index (t->fbos, Fbo, 0);
}
static Texture *
find_texture_by_size (GHashTable *textures,
int width,
int height)
{
GHashTableIter iter;
gpointer value_p = NULL;
g_hash_table_iter_init (&iter, textures);
while (g_hash_table_iter_next (&iter, NULL, &value_p))
{
Texture *t = value_p;
if (t->width == width && t->height == height)
return t;
}
return NULL;
}
static Texture *
create_texture (GskGLDriver *driver,
int width,
int height)
{
guint texture_id;
Texture *t;
if (width >= driver->max_texture_size ||
height >= driver->max_texture_size)
{
g_critical ("Texture %d x %d is bigger than supported texture limit of %d; clipping...",
width, height,
driver->max_texture_size);
width = MIN (width, driver->max_texture_size);
height = MIN (height, driver->max_texture_size);
}
t = find_texture_by_size (driver->textures, width, height);
if (t != NULL && !t->in_use && t->user == NULL)
{
GSK_NOTE (OPENGL, g_print ("Reusing Texture(%d) for size %dx%d\n",
t->texture_id, t->width, t->height));
t->in_use = TRUE;
return t;
}
glGenTextures (1, &texture_id);
t = texture_new ();
t->texture_id = texture_id;
t->width = width;
t->height = height;
t->min_filter = GL_NEAREST;
t->mag_filter = GL_NEAREST;
t->in_use = TRUE;
g_hash_table_insert (driver->textures, GINT_TO_POINTER (texture_id), t);
return t;
}
static void
gsk_gl_driver_release_texture (gpointer data)
{
Texture *t = data;
t->user = NULL;
}
int
gsk_gl_driver_get_texture_for_texture (GskGLDriver *driver,
GskTexture *texture,
int min_filter,
int mag_filter)
{
Texture *t;
cairo_surface_t *surface;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
g_return_val_if_fail (GSK_IS_TEXTURE (texture), -1);
t = gsk_texture_get_render_data (texture, driver);
if (t)
{
if (t->min_filter == min_filter && t->mag_filter == mag_filter)
return t->texture_id;
}
t = create_texture (driver, gsk_texture_get_width (texture), gsk_texture_get_height (texture));
if (gsk_texture_set_render_data (texture, driver, t, gsk_gl_driver_release_texture))
t->user = texture;
surface = gsk_texture_download (texture);
gsk_gl_driver_bind_source_texture (driver, t->texture_id);
gsk_gl_driver_init_texture_with_surface (driver,
t->texture_id,
surface,
min_filter,
mag_filter);
return t->texture_id;
}
int
gsk_gl_driver_create_texture (GskGLDriver *driver,
int width,
int height)
{
Texture *t;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
t = create_texture (driver, width, height);
return t->texture_id;
}
static Vao *
find_vao (GHashTable *vaos,
int position_id,
int uv_id,
int n_quads,
GskQuadVertex *quads)
{
GHashTableIter iter;
gpointer value_p = NULL;
g_hash_table_iter_init (&iter, vaos);
while (g_hash_table_iter_next (&iter, NULL, &value_p))
{
Vao *v = value_p;
if (v->position_id != position_id || v->uv_id != uv_id)
continue;
if (v->n_quads != n_quads)
continue;
if (memcmp (v->quads, quads, sizeof (GskQuadVertex) * n_quads) == 0)
return v;
}
return NULL;
}
int
gsk_gl_driver_create_vao_for_quad (GskGLDriver *driver,
int position_id,
int uv_id,
int n_quads,
GskQuadVertex *quads)
{
GLuint vao_id, buffer_id;
Vao *v;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
g_return_val_if_fail (driver->in_frame, -1);
v = find_vao (driver->vaos, position_id, uv_id, n_quads, quads);
if (v != NULL && !v->in_use)
{
GSK_NOTE (OPENGL, g_print ("Reusing VAO(%d)\n", v->vao_id));
v->in_use = TRUE;
return v->vao_id;
}
glGenVertexArrays (1, &vao_id);
glBindVertexArray (vao_id);
glGenBuffers (1, &buffer_id);
glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
glBufferData (GL_ARRAY_BUFFER, sizeof (GskQuadVertex) * n_quads, quads, GL_STATIC_DRAW);
glEnableVertexAttribArray (position_id);
glVertexAttribPointer (position_id, 2, GL_FLOAT, GL_FALSE,
sizeof (GskQuadVertex),
(void *) G_STRUCT_OFFSET (GskQuadVertex, position));
glEnableVertexAttribArray (uv_id);
glVertexAttribPointer (uv_id, 2, GL_FLOAT, GL_FALSE,
sizeof (GskQuadVertex),
(void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
glBindBuffer (GL_ARRAY_BUFFER, 0);
glBindVertexArray (0);
v = vao_new ();
v->vao_id = vao_id;
v->buffer_id = buffer_id;
v->position_id = position_id;
v->uv_id = uv_id;
v->n_quads = n_quads;
v->quads = g_memdup (quads, sizeof (GskQuadVertex) * n_quads);
v->in_use = TRUE;
g_hash_table_insert (driver->vaos, GINT_TO_POINTER (vao_id), v);
#ifdef G_ENABLE_DEBUG
if (GSK_DEBUG_CHECK (OPENGL))
{
int i;
g_print ("New VAO(%d) for quad[%d] : {\n", v->vao_id, n_quads);
for (i = 0; i < n_quads; i++)
{
g_print (" { x:%.2f, y:%.2f } { u:%.2f, v:%.2f }\n",
quads[i].position[0], quads[i].position[1],
quads[i].uv[0], quads[i].uv[1]);
}
g_print ("}\n");
}
#endif
return vao_id;
}
int
gsk_gl_driver_create_render_target (GskGLDriver *driver,
int texture_id,
gboolean add_depth_buffer,
gboolean add_stencil_buffer)
{
GLuint fbo_id, depth_stencil_buffer_id;
Texture *t;
Fbo f;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
g_return_val_if_fail (driver->in_frame, -1);
t = gsk_gl_driver_get_texture (driver, texture_id);
if (t == NULL)
return -1;
if (t->fbos == NULL)
{
t->fbos = g_array_new (FALSE, FALSE, sizeof (Fbo));
g_array_set_clear_func (t->fbos, fbo_clear);
}
glGenFramebuffers (1, &fbo_id);
glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, t->texture_id, 0);
if (add_depth_buffer || add_stencil_buffer)
glGenRenderbuffersEXT (1, &depth_stencil_buffer_id);
else
depth_stencil_buffer_id = 0;
glBindRenderbuffer (GL_RENDERBUFFER, depth_stencil_buffer_id);
if (add_depth_buffer || add_stencil_buffer)
{
if (add_stencil_buffer)
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, t->width, t->height);
else
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, t->width, t->height);
if (add_depth_buffer)
glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depth_stencil_buffer_id);
if (add_stencil_buffer)
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, depth_stencil_buffer_id);
}
f.fbo_id = fbo_id;
f.depth_stencil_id = depth_stencil_buffer_id;
g_array_append_val (t->fbos, f);
glBindFramebuffer (GL_FRAMEBUFFER, driver->default_fbo.fbo_id);
return fbo_id;
}
void
gsk_gl_driver_bind_source_texture (GskGLDriver *driver,
int texture_id)
{
Texture *t;
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_return_if_fail (driver->in_frame);
t = gsk_gl_driver_get_texture (driver, texture_id);
if (t == NULL)
{
g_critical ("No texture %d found.", texture_id);
return;
}
if (driver->bound_source_texture != t)
{
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, t->texture_id);
driver->bound_source_texture = t;
}
}
void
gsk_gl_driver_bind_mask_texture (GskGLDriver *driver,
int texture_id)
{
Texture *t;
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_return_if_fail (driver->in_frame);
t = gsk_gl_driver_get_texture (driver, texture_id);
if (t == NULL)
{
g_critical ("No texture %d found.", texture_id);
return;
}
if (driver->bound_mask_texture != t)
{
glActiveTexture (GL_TEXTURE0 + 1);
glBindTexture (GL_TEXTURE_2D, t->texture_id);
glActiveTexture (GL_TEXTURE0);
driver->bound_mask_texture = t;
}
}
void
gsk_gl_driver_bind_vao (GskGLDriver *driver,
int vao_id)
{
Vao *v;
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_return_if_fail (driver->in_frame);
v = gsk_gl_driver_get_vao (driver, vao_id);
if (v == NULL)
{
g_critical ("No VAO %d found.", vao_id);
return;
}
if (driver->bound_vao != v)
{
glBindVertexArray (v->vao_id);
glBindBuffer (GL_ARRAY_BUFFER, v->buffer_id);
glEnableVertexAttribArray (v->position_id);
glEnableVertexAttribArray (v->uv_id);
driver->bound_vao = v;
}
}
gboolean
gsk_gl_driver_bind_render_target (GskGLDriver *driver,
int texture_id)
{
int status;
Fbo *f;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), FALSE);
g_return_val_if_fail (driver->in_frame, FALSE);
f = gsk_gl_driver_get_fbo (driver, texture_id);
if (f == NULL)
{
g_critical ("No render target associated to texture %d found.", texture_id);
return FALSE;
}
if (f != driver->bound_fbo)
{
glBindFramebuffer (GL_FRAMEBUFFER, f->fbo_id);
driver->bound_fbo = f;
}
status = glCheckFramebufferStatus (GL_FRAMEBUFFER);
return status == GL_FRAMEBUFFER_COMPLETE;
}
void
gsk_gl_driver_destroy_texture (GskGLDriver *driver,
int texture_id)
{
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_hash_table_remove (driver->textures, GINT_TO_POINTER (texture_id));
}
void
gsk_gl_driver_destroy_vao (GskGLDriver *driver,
int vao_id)
{
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
g_hash_table_remove (driver->vaos, GINT_TO_POINTER (vao_id));
}
static void
gsk_gl_driver_set_texture_parameters (GskGLDriver *driver,
int min_filter,
int mag_filter)
{
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void
gsk_gl_driver_init_texture_empty (GskGLDriver *driver,
int texture_id)
{
Texture *t;
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
t = gsk_gl_driver_get_texture (driver, texture_id);
if (t == NULL)
{
g_critical ("No texture %d found.", texture_id);
return;
}
if (!(driver->bound_source_texture == t || driver->bound_mask_texture == t))
{
g_critical ("You must bind the texture before initializing it.");
return;
}
gsk_gl_driver_set_texture_parameters (driver, t->min_filter, t->mag_filter);
if (gdk_gl_context_get_use_es (driver->gl_context))
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, t->width, t->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture (GL_TEXTURE_2D, 0);
}
void
gsk_gl_driver_init_texture_with_surface (GskGLDriver *driver,
int texture_id,
cairo_surface_t *surface,
int min_filter,
int mag_filter)
{
Texture *t;
g_return_if_fail (GSK_IS_GL_DRIVER (driver));
t = gsk_gl_driver_get_texture (driver, texture_id);
if (t == NULL)
{
g_critical ("No texture %d found.", texture_id);
return;
}
if (!(driver->bound_source_texture == t || driver->bound_mask_texture == t))
{
g_critical ("You must bind the texture before initializing it.");
return;
}
gsk_gl_driver_set_texture_parameters (driver, min_filter, mag_filter);
gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, t->width, t->height, NULL);
t->min_filter = min_filter;
t->mag_filter = mag_filter;
if (t->min_filter != GL_NEAREST)
glGenerateMipmap (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, 0);
}