gtk2/gsk/gskgldriver.c
Emmanuele Bassi 24a6f3c055 gsk: Move resource handling inside GskGLDriver
We want the GL driver to cache as many resources as possible, so we can
always ensure that we're in a consistent state, and we can handle state
transitions more appropriately.
2016-10-18 11:49:09 +01:00

603 lines
15 KiB
C

#include "config.h"
#include "gskgldriverprivate.h"
#include "gskdebugprivate.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;
} Texture;
typedef struct {
GLuint vao_id;
GLuint buffer_id;
GLuint position_id;
GLuint uv_id;
} 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;
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;
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;
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);
g_clear_pointer (&self->textures, g_hash_table_unref);
g_clear_pointer (&self->vaos, g_hash_table_unref);
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);
}
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;
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;
}
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);
}
int
gsk_gl_driver_create_texture (GskGLDriver *driver,
int width,
int height)
{
guint texture_id;
Texture *t;
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), -1);
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;
g_hash_table_insert (driver->textures, GINT_TO_POINTER (texture_id), t);
return t->texture_id;
}
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);
glGenVertexArrays (1, &vao_id);
glBindVertexArray (vao_id);
glGenBuffers (1, &buffer_id);
glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
glBufferData (GL_ARRAY_BUFFER, 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;
g_hash_table_insert (driver->vaos, GINT_TO_POINTER (vao_id), v);
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);
}