mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-11 03:10:09 +00:00
7afdd3fdb5
GSK is conceptually split into two scene graphs: * a simple rendering tree of operations * a complex set of logical layers The latter is built on the former, and adds convenience and high level API for application developers. The lower layer, though, is what gets transformed into the rendering pipeline, as it's simple and thus can be transformed into appropriate rendering commands with minimal state changes. The lower layer is also suitable for reuse from more complex higher layers, like the CSS machinery in GTK, without necessarily port those layers to the GSK high level API. This lower layer is based on GskRenderNode instances, which represent the tree of rendering operations; and a GskRenderer instance, which takes the render nodes and submits them (after potentially reordering and transforming them to a more appropriate representation) to the underlying graphic system.
1093 lines
31 KiB
C
1093 lines
31 KiB
C
#include "config.h"
|
|
|
|
#include "gskglrendererprivate.h"
|
|
|
|
#include "gskdebugprivate.h"
|
|
#include "gskenums.h"
|
|
#include "gskrendererprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskrendernodeiter.h"
|
|
|
|
#include "gskprivate.h"
|
|
|
|
#include <epoxy/gl.h>
|
|
|
|
typedef struct {
|
|
/* Back pointer to the node, only meant for comparison */
|
|
GskRenderNode *node;
|
|
|
|
graphene_point3d_t min;
|
|
graphene_point3d_t max;
|
|
|
|
graphene_size_t size;
|
|
|
|
graphene_matrix_t mvp;
|
|
|
|
gboolean opaque : 1;
|
|
float opacity;
|
|
float z;
|
|
|
|
const char *name;
|
|
|
|
guint vao_id;
|
|
guint texture_id;
|
|
guint program_id;
|
|
guint mvp_location;
|
|
guint map_location;
|
|
guint uv_location;
|
|
guint position_location;
|
|
guint alpha_location;
|
|
guint buffer_id;
|
|
} RenderItem;
|
|
|
|
struct _GskGLRenderer
|
|
{
|
|
GskRenderer parent_instance;
|
|
|
|
GdkGLContext *context;
|
|
|
|
graphene_matrix_t mvp;
|
|
graphene_frustum_t frustum;
|
|
|
|
guint frame_buffer;
|
|
guint render_buffer;
|
|
guint depth_stencil_buffer;
|
|
guint texture_id;
|
|
|
|
guint program_id;
|
|
guint mvp_location;
|
|
guint map_location;
|
|
guint uv_location;
|
|
guint position_location;
|
|
guint alpha_location;
|
|
|
|
guint vao_id;
|
|
|
|
GArray *opaque_render_items;
|
|
GArray *transparent_render_items;
|
|
|
|
gboolean has_buffers : 1;
|
|
gboolean has_alpha : 1;
|
|
gboolean has_stencil_buffer : 1;
|
|
gboolean has_depth_buffer : 1;
|
|
};
|
|
|
|
struct _GskGLRendererClass
|
|
{
|
|
GskRendererClass parent_class;
|
|
};
|
|
|
|
static void render_item_clear (gpointer data_);
|
|
|
|
G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
|
|
|
|
static void
|
|
gsk_gl_renderer_dispose (GObject *gobject)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (gobject);
|
|
|
|
g_clear_object (&self->context);
|
|
|
|
G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_create_buffers (GskGLRenderer *self)
|
|
{
|
|
if (self->has_buffers)
|
|
return;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Creating buffers\n"));
|
|
|
|
glGenFramebuffersEXT (1, &self->frame_buffer);
|
|
|
|
if (gsk_renderer_get_use_alpha (GSK_RENDERER (self)))
|
|
{
|
|
if (self->texture_id == 0)
|
|
glGenTextures (1, &self->texture_id);
|
|
|
|
if (self->render_buffer != 0)
|
|
{
|
|
glDeleteRenderbuffersEXT (1, &self->render_buffer);
|
|
self->render_buffer = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (self->render_buffer == 0)
|
|
glGenRenderbuffersEXT (1, &self->render_buffer);
|
|
|
|
if (self->texture_id != 0)
|
|
{
|
|
glDeleteTextures (1, &self->texture_id);
|
|
self->texture_id = 0;
|
|
}
|
|
}
|
|
|
|
if (self->has_depth_buffer || self->has_stencil_buffer)
|
|
{
|
|
if (self->depth_stencil_buffer == 0)
|
|
glGenRenderbuffersEXT (1, &self->depth_stencil_buffer);
|
|
}
|
|
else
|
|
{
|
|
if (self->depth_stencil_buffer != 0)
|
|
{
|
|
glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer);
|
|
self->depth_stencil_buffer = 0;
|
|
}
|
|
}
|
|
|
|
/* We only have one VAO at the moment */
|
|
glGenVertexArrays (1, &self->vao_id);
|
|
glBindVertexArray (self->vao_id);
|
|
|
|
self->has_buffers = TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_allocate_buffers (GskGLRenderer *self,
|
|
int width,
|
|
int height)
|
|
{
|
|
if (self->context == NULL)
|
|
return;
|
|
|
|
if (self->texture_id != 0)
|
|
{
|
|
glBindTexture (GL_TEXTURE_2D, self->texture_id);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
|
}
|
|
|
|
if (self->render_buffer != 0)
|
|
{
|
|
glBindRenderbuffer (GL_RENDERBUFFER, self->render_buffer);
|
|
glRenderbufferStorage (GL_RENDERBUFFER, GL_RGB8, width, height);
|
|
}
|
|
|
|
if (self->has_depth_buffer || self->has_stencil_buffer)
|
|
{
|
|
glBindRenderbuffer (GL_RENDERBUFFER, self->depth_stencil_buffer);
|
|
|
|
if (self->has_stencil_buffer)
|
|
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
|
else
|
|
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_attach_buffers (GskGLRenderer *self)
|
|
{
|
|
gsk_gl_renderer_create_buffers (self);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Attaching buffers\n"));
|
|
|
|
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->frame_buffer);
|
|
|
|
if (self->texture_id != 0)
|
|
{
|
|
glFramebufferTexture2D (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
|
GL_TEXTURE_2D, self->texture_id, 0);
|
|
}
|
|
else if (self->render_buffer != 0)
|
|
{
|
|
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
|
GL_RENDERBUFFER_EXT, self->render_buffer);
|
|
}
|
|
|
|
if (self->depth_stencil_buffer != 0)
|
|
{
|
|
if (self->has_depth_buffer)
|
|
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
|
|
GL_RENDERBUFFER_EXT, self->depth_stencil_buffer);
|
|
|
|
if (self->has_stencil_buffer)
|
|
glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
|
|
GL_RENDERBUFFER_EXT, self->depth_stencil_buffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
|
|
{
|
|
if (self->context == NULL)
|
|
return;
|
|
|
|
if (!self->has_buffers)
|
|
return;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Destroying buffers\n"));
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
|
|
if (self->vao_id != 0)
|
|
{
|
|
glDeleteVertexArrays (1, &self->vao_id);
|
|
self->vao_id = 0;
|
|
}
|
|
|
|
if (self->depth_stencil_buffer != 0)
|
|
{
|
|
glDeleteRenderbuffersEXT (1, &self->depth_stencil_buffer);
|
|
self->depth_stencil_buffer = 0;
|
|
}
|
|
|
|
if (self->render_buffer != 0)
|
|
{
|
|
glDeleteRenderbuffersEXT (1, &self->render_buffer);
|
|
self->render_buffer = 0;
|
|
}
|
|
|
|
if (self->texture_id != 0)
|
|
{
|
|
glDeleteTextures (1, &self->texture_id);
|
|
self->texture_id = 0;
|
|
}
|
|
|
|
if (self->frame_buffer != 0)
|
|
{
|
|
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
|
|
glDeleteFramebuffersEXT (1, &self->frame_buffer);
|
|
self->frame_buffer = 0;
|
|
}
|
|
|
|
self->has_buffers = FALSE;
|
|
}
|
|
|
|
static guint
|
|
create_shader (int type,
|
|
const char *code)
|
|
{
|
|
guint shader;
|
|
int status;
|
|
|
|
shader = glCreateShader (type);
|
|
glShaderSource (shader, 1, &code, NULL);
|
|
glCompileShader (shader);
|
|
|
|
glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
|
|
if (status == GL_FALSE)
|
|
{
|
|
int log_len;
|
|
char *buffer;
|
|
|
|
glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &log_len);
|
|
|
|
buffer = g_malloc0 (log_len + 1);
|
|
glGetShaderInfoLog (shader, log_len, NULL, buffer);
|
|
|
|
g_critical ("Compile failure in %s shader:\n%s",
|
|
type == GL_VERTEX_SHADER ? "vertex" : "fragment",
|
|
buffer);
|
|
g_free (buffer);
|
|
|
|
glDeleteShader (shader);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_create_program (GskGLRenderer *self)
|
|
{
|
|
guint vertex_shader = 0, fragment_shader = 0;
|
|
GBytes *source;
|
|
int status;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Compiling vertex shader\n"));
|
|
source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-vertex.glsl", 0, NULL);
|
|
vertex_shader = create_shader (GL_VERTEX_SHADER, g_bytes_get_data (source, NULL));
|
|
g_bytes_unref (source);
|
|
if (vertex_shader == 0)
|
|
goto out;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Compiling fragment shader\n"));
|
|
source = g_resources_lookup_data ("/org/gtk/libgsk/glsl/base-renderer-fragment.glsl", 0, NULL);
|
|
fragment_shader = create_shader (GL_FRAGMENT_SHADER, g_bytes_get_data (source, NULL));
|
|
g_bytes_unref (source);
|
|
if (fragment_shader == 0)
|
|
goto out;
|
|
|
|
self->program_id = glCreateProgram ();
|
|
glAttachShader (self->program_id, vertex_shader);
|
|
glAttachShader (self->program_id, fragment_shader);
|
|
glLinkProgram (self->program_id);
|
|
|
|
glGetProgramiv (self->program_id, GL_LINK_STATUS, &status);
|
|
if (status == GL_FALSE)
|
|
{
|
|
char *buffer = NULL;
|
|
int log_len = 0;
|
|
|
|
glGetProgramiv (self->program_id, GL_INFO_LOG_LENGTH, &log_len);
|
|
|
|
buffer = g_malloc0 (log_len + 1);
|
|
glGetProgramInfoLog (self->program_id, log_len, NULL, buffer);
|
|
|
|
g_critical ("Linking failure in shader:\n%s", buffer);
|
|
g_free (buffer);
|
|
|
|
glDeleteProgram (self->program_id);
|
|
self->program_id = 0;
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* Find the location of each uniform and attribute we use in our
|
|
* shaders
|
|
*/
|
|
self->mvp_location = glGetUniformLocation (self->program_id, "mvp");
|
|
self->map_location = glGetUniformLocation (self->program_id, "map");
|
|
self->alpha_location = glGetUniformLocation (self->program_id, "alpha");
|
|
self->position_location = glGetAttribLocation (self->program_id, "position");
|
|
self->uv_location = glGetAttribLocation (self->program_id, "uv");
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Program [%d] { mvp:%u, map:%u, alpha:%u, position:%u, uv:%u }\n",
|
|
self->program_id,
|
|
self->mvp_location,
|
|
self->map_location,
|
|
self->alpha_location,
|
|
self->position_location,
|
|
self->uv_location));
|
|
|
|
/* We can detach and destroy the shaders from the linked program */
|
|
glDetachShader (self->program_id, vertex_shader);
|
|
glDetachShader (self->program_id, fragment_shader);
|
|
|
|
out:
|
|
if (vertex_shader != 0)
|
|
glDeleteShader (vertex_shader);
|
|
if (fragment_shader != 0)
|
|
glDeleteShader (fragment_shader);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_destroy_program (GskGLRenderer *self)
|
|
{
|
|
if (self->program_id != 0)
|
|
{
|
|
glDeleteProgram (self->program_id);
|
|
self->program_id = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_realize (GskRenderer *renderer)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
GError *error = NULL;
|
|
|
|
/* If we didn't get a GdkGLContext before realization, try creating
|
|
* one now, for our exclusive use.
|
|
*/
|
|
if (self->context == NULL)
|
|
{
|
|
GdkWindow *window = gsk_renderer_get_window (renderer);
|
|
|
|
if (window == NULL)
|
|
return FALSE;
|
|
|
|
self->context = gdk_window_create_gl_context (window, &error);
|
|
if (error != NULL)
|
|
{
|
|
g_critical ("Unable to create GL context for renderer: %s",
|
|
error->message);
|
|
g_error_free (error);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gdk_gl_context_realize (self->context, &error);
|
|
if (error != NULL)
|
|
{
|
|
g_critical ("Unable to realize GL renderer: %s", error->message);
|
|
g_error_free (error);
|
|
return FALSE;
|
|
}
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
|
|
|
|
gsk_gl_renderer_create_buffers (self);
|
|
gsk_gl_renderer_create_program (self);
|
|
|
|
self->opaque_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16);
|
|
g_array_set_clear_func (self->opaque_render_items, render_item_clear);
|
|
|
|
self->transparent_render_items = g_array_sized_new (FALSE, FALSE, sizeof (RenderItem), 16);
|
|
g_array_set_clear_func (self->opaque_render_items, render_item_clear);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_unrealize (GskRenderer *renderer)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
|
|
if (self->context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
|
|
g_clear_pointer (&self->opaque_render_items, g_array_unref);
|
|
g_clear_pointer (&self->transparent_render_items, g_array_unref);
|
|
|
|
gsk_gl_renderer_destroy_buffers (self);
|
|
gsk_gl_renderer_destroy_program (self);
|
|
|
|
if (self->context == gdk_gl_context_get_current ())
|
|
gdk_gl_context_clear_current ();
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_resize_viewport (GskRenderer *renderer,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_update (GskRenderer *renderer,
|
|
const graphene_matrix_t *modelview,
|
|
const graphene_matrix_t *projection)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Updating the modelview/projection\n"));
|
|
|
|
graphene_matrix_multiply (modelview, projection, &self->mvp);
|
|
|
|
graphene_frustum_init_from_matrix (&self->frustum, &self->mvp);
|
|
}
|
|
|
|
static void
|
|
render_item_clear (gpointer data_)
|
|
{
|
|
RenderItem *item = data_;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] buffer %u\n",
|
|
item,
|
|
item->buffer_id));
|
|
glDeleteBuffers (1, &item->buffer_id);
|
|
item->buffer_id = 0;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Destroying render item [%p] texture %u\n",
|
|
item,
|
|
item->texture_id));
|
|
glDeleteTextures (1, &item->texture_id);
|
|
item->texture_id = 0;
|
|
|
|
graphene_matrix_init_identity (&item->mvp);
|
|
|
|
item->opacity = 1;
|
|
}
|
|
|
|
#define N_VERTICES 6
|
|
|
|
static void
|
|
render_item (RenderItem *item)
|
|
{
|
|
struct vertex_info {
|
|
float position[2];
|
|
float uv[2];
|
|
};
|
|
float mvp[16];
|
|
|
|
glBindVertexArray (item->vao_id);
|
|
|
|
/* Generate the vertex buffer for the texture quad */
|
|
if (item->buffer_id == 0)
|
|
{
|
|
struct vertex_info vertex_data[] = {
|
|
{ { item->min.x, item->min.y }, { 0, 0 }, },
|
|
{ { item->min.x, item->max.y }, { 0, 1 }, },
|
|
{ { item->max.x, item->min.y }, { 1, 0 }, },
|
|
|
|
{ { item->max.x, item->max.y }, { 1, 1 }, },
|
|
{ { item->min.x, item->max.y }, { 0, 1 }, },
|
|
{ { item->max.x, item->min.y }, { 1, 0 }, },
|
|
};
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Creating quad for render item [%p]\n", item));
|
|
|
|
glGenBuffers (1, &item->buffer_id);
|
|
glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id);
|
|
|
|
/* The data won't change */
|
|
glBufferData (GL_ARRAY_BUFFER, sizeof (vertex_data), vertex_data, GL_STATIC_DRAW);
|
|
|
|
/* Set up the buffers with the computed position and texels */
|
|
glEnableVertexAttribArray (item->position_location);
|
|
glVertexAttribPointer (item->position_location, 2, GL_FLOAT, GL_FALSE,
|
|
sizeof (struct vertex_info),
|
|
(void *) G_STRUCT_OFFSET (struct vertex_info, position));
|
|
glEnableVertexAttribArray (item->uv_location);
|
|
glVertexAttribPointer (item->uv_location, 2, GL_FLOAT, GL_FALSE,
|
|
sizeof (struct vertex_info),
|
|
(void *) G_STRUCT_OFFSET (struct vertex_info, uv));
|
|
}
|
|
else
|
|
{
|
|
/* We already set up the vertex buffer, so we just need to reuse it */
|
|
glBindBuffer (GL_ARRAY_BUFFER, item->buffer_id);
|
|
glEnableVertexAttribArray (item->position_location);
|
|
glEnableVertexAttribArray (item->uv_location);
|
|
}
|
|
|
|
glUseProgram (item->program_id);
|
|
|
|
/* Use texture unit 0 for the sampler */
|
|
glActiveTexture (GL_TEXTURE0);
|
|
glBindTexture (GL_TEXTURE_2D, item->texture_id);
|
|
glUniform1i (item->map_location, 0);
|
|
|
|
/* Pass the opacity component */
|
|
glUniform1f (item->alpha_location, item->opaque ? 1 : item->opacity);
|
|
|
|
/* Pass the mvp to the vertex shader */
|
|
GSK_NOTE (OPENGL, graphene_matrix_print (&item->mvp));
|
|
graphene_matrix_to_float (&item->mvp, mvp);
|
|
glUniformMatrix4fv (item->mvp_location, 1, GL_FALSE, mvp);
|
|
|
|
/* Draw the quad */
|
|
GSK_NOTE (OPENGL, g_print ("Drawing item <%s>[%p] with opacity: %g\n",
|
|
item->name,
|
|
item,
|
|
item->opaque ? 1 : item->opacity));
|
|
|
|
glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
|
|
|
|
/* Reset the state */
|
|
glBindTexture (GL_TEXTURE_2D, 0);
|
|
glDisableVertexAttribArray (item->position_location);
|
|
glDisableVertexAttribArray (item->uv_location);
|
|
glUseProgram (0);
|
|
}
|
|
|
|
static void
|
|
surface_to_texture (cairo_surface_t *surface,
|
|
graphene_rect_t *clip,
|
|
int min_filter,
|
|
int mag_filter,
|
|
guint *texture_out)
|
|
{
|
|
cairo_surface_t *tmp;
|
|
guint texture_id;
|
|
|
|
cairo_surface_flush (surface);
|
|
|
|
tmp = cairo_surface_map_to_image (surface, &(cairo_rectangle_int_t) {
|
|
0, 0,
|
|
clip->size.width,
|
|
clip->size.height
|
|
});
|
|
|
|
glGenTextures (1, &texture_id);
|
|
glBindTexture (GL_TEXTURE_2D, texture_id);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Uploading px@[%p] { w:%g, h:%g } to texid:%d\n",
|
|
cairo_image_surface_get_data (tmp),
|
|
clip->size.width,
|
|
clip->size.height,
|
|
texture_id));
|
|
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
|
|
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
|
|
|
glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
|
|
glPixelStorei (GL_UNPACK_ROW_LENGTH, cairo_image_surface_get_stride (tmp) / 4);
|
|
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
|
|
clip->size.width,
|
|
clip->size.height,
|
|
0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
|
cairo_image_surface_get_data (tmp));
|
|
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
|
|
|
|
if (min_filter != GL_NEAREST)
|
|
glGenerateMipmap (GL_TEXTURE_2D);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("New texture id %d from surface %p\n", texture_id, surface));
|
|
|
|
cairo_surface_unmap_image (surface, tmp);
|
|
|
|
*texture_out = texture_id;
|
|
}
|
|
|
|
static void
|
|
get_gl_scaling_filters (GskRenderer *renderer,
|
|
int *min_filter_r,
|
|
int *mag_filter_r)
|
|
{
|
|
GskScalingFilter min_filter, mag_filter;
|
|
|
|
gsk_renderer_get_scaling_filters (renderer, &min_filter, &mag_filter);
|
|
|
|
switch (min_filter)
|
|
{
|
|
case GSK_SCALING_FILTER_NEAREST:
|
|
*min_filter_r = GL_NEAREST;
|
|
break;
|
|
|
|
case GSK_SCALING_FILTER_LINEAR:
|
|
*min_filter_r = GL_LINEAR;
|
|
break;
|
|
|
|
case GSK_SCALING_FILTER_TRILINEAR:
|
|
*min_filter_r = GL_LINEAR_MIPMAP_LINEAR;
|
|
break;
|
|
}
|
|
|
|
switch (mag_filter)
|
|
{
|
|
case GSK_SCALING_FILTER_NEAREST:
|
|
*mag_filter_r = GL_NEAREST;
|
|
break;
|
|
|
|
/* There's no point in using anything above GL_LINEAR for
|
|
* magnification filters
|
|
*/
|
|
case GSK_SCALING_FILTER_LINEAR:
|
|
case GSK_SCALING_FILTER_TRILINEAR:
|
|
*mag_filter_r = GL_LINEAR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
check_in_frustum (const graphene_frustum_t *frustum,
|
|
RenderItem *item)
|
|
{
|
|
graphene_box_t aabb;
|
|
|
|
graphene_box_init (&aabb, &item->min, &item->max);
|
|
graphene_matrix_transform_box (&item->mvp, &aabb, &aabb);
|
|
|
|
return graphene_frustum_intersects_box (frustum, &aabb);
|
|
}
|
|
|
|
static float
|
|
project_item (const graphene_matrix_t *projection,
|
|
const graphene_matrix_t *modelview)
|
|
{
|
|
graphene_vec4_t vec;
|
|
|
|
graphene_matrix_get_row (modelview, 3, &vec);
|
|
graphene_matrix_transform_vec4 (projection, &vec, &vec);
|
|
|
|
return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_add_render_item (GskGLRenderer *self,
|
|
GskRenderNode *node)
|
|
{
|
|
graphene_rect_t viewport;
|
|
int gl_min_filter, gl_mag_filter;
|
|
cairo_surface_t *surface;
|
|
GskRenderNodeIter iter;
|
|
graphene_matrix_t mv, projection;
|
|
graphene_rect_t bounds;
|
|
GskRenderNode *child;
|
|
RenderItem item;
|
|
|
|
if (gsk_render_node_is_hidden (node))
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("Skipping hidden node <%s>[%p]\n",
|
|
node->name != NULL ? node->name : "unnamed",
|
|
node));
|
|
return;
|
|
}
|
|
|
|
gsk_renderer_get_viewport (GSK_RENDERER (self), &viewport);
|
|
|
|
gsk_render_node_get_bounds (node, &bounds);
|
|
|
|
item.node = node;
|
|
item.name = node->name != NULL ? node->name : "unnamed";
|
|
|
|
/* The texture size */
|
|
item.size = bounds.size;
|
|
|
|
/* Each render item is an axis-aligned bounding box that we
|
|
* transform using the given transformation matrix
|
|
*/
|
|
item.min.x = (bounds.origin.x * 2) / bounds.size.width - 1;
|
|
item.min.y = (bounds.origin.y * 2) / bounds.size.height - 1;
|
|
item.min.z = 0.f;
|
|
|
|
item.max.x = (bounds.origin.x + bounds.size.width) * 2 / bounds.size.width - 1;
|
|
item.max.y = (bounds.origin.y + bounds.size.height) * 2 / bounds.size.height - 1;
|
|
item.max.z = 0.f;
|
|
|
|
/* The location of the item, in normalized world coordinates */
|
|
gsk_render_node_get_world_matrix (node, &mv);
|
|
item.mvp = mv;
|
|
|
|
item.opaque = gsk_render_node_is_opaque (node);
|
|
item.opacity = gsk_render_node_get_opacity (node);
|
|
|
|
/* GL objects */
|
|
item.vao_id = self->vao_id;
|
|
item.buffer_id = 0;
|
|
item.program_id = self->program_id;
|
|
item.map_location = self->map_location;
|
|
item.mvp_location = self->mvp_location;
|
|
item.uv_location = self->uv_location;
|
|
item.position_location = self->position_location;
|
|
item.alpha_location = self->alpha_location;
|
|
|
|
gsk_renderer_get_projection (GSK_RENDERER (self), &projection);
|
|
item.z = project_item (&projection, &mv);
|
|
|
|
/* Discard the item if it's outside of the frustum as determined by the
|
|
* viewport and the projection matrix
|
|
*/
|
|
#if 0
|
|
if (!check_in_frustum (&self->frustum, &item))
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("Node <%s>[%p] culled by frustum\n",
|
|
node->name != NULL ? node->name : "unnamed",
|
|
node));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* TODO: This should really be an asset atlas, to avoid uploading a ton
|
|
* of textures. Ideally we could use a single Cairo surface to get around
|
|
* the GL texture limits and reorder the texture data on the CPU side and
|
|
* do a single upload; alternatively, we could use a separate FBO and
|
|
* render each texture into it
|
|
*/
|
|
get_gl_scaling_filters (GSK_RENDERER (self), &gl_min_filter, &gl_mag_filter);
|
|
surface = gsk_render_node_get_surface (node);
|
|
|
|
/* If the node does not have any surface we skip drawing it, but we still
|
|
* recurse.
|
|
*
|
|
* XXX: This needs to be re-done if the opacity is != 0, in which case we
|
|
* need to composite the opacity level of the children
|
|
*/
|
|
if (surface == NULL)
|
|
goto recurse_children;
|
|
|
|
surface_to_texture (surface, &bounds, gl_min_filter, gl_mag_filter, &item.texture_id);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n",
|
|
node->name != NULL ? node->name : "unnamed",
|
|
node));
|
|
if (gsk_render_node_is_opaque (node) && gsk_render_node_get_opacity (node) == 1.f)
|
|
g_array_append_val (self->opaque_render_items, item);
|
|
else
|
|
g_array_append_val (self->transparent_render_items, item);
|
|
|
|
recurse_children:
|
|
gsk_render_node_iter_init (&iter, node);
|
|
while (gsk_render_node_iter_next (&iter, &child))
|
|
gsk_gl_renderer_add_render_item (self, child);
|
|
}
|
|
|
|
static int
|
|
opaque_item_cmp (gconstpointer _a,
|
|
gconstpointer _b)
|
|
{
|
|
const RenderItem *a = _a;
|
|
const RenderItem *b = _b;
|
|
|
|
if (a->z != b->z)
|
|
{
|
|
if (a->z > b->z)
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (a != b)
|
|
{
|
|
if ((gsize) a > (gsize) b)
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
transparent_item_cmp (gconstpointer _a,
|
|
gconstpointer _b)
|
|
{
|
|
const RenderItem *a = _a;
|
|
const RenderItem *b = _b;
|
|
|
|
if (a->z != b->z)
|
|
{
|
|
if (a->z < b->z)
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (a != b)
|
|
{
|
|
if ((gsize) a < (gsize) b)
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_validate_tree (GskRenderer *renderer,
|
|
GskRenderNode *root)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
gboolean clear_items = FALSE;
|
|
int i;
|
|
|
|
if (self->context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
|
|
if (self->opaque_render_items->len > 0 || self->transparent_render_items->len > 0)
|
|
{
|
|
/* If we only changed the opacity and transformations then there is no
|
|
* reason to clear the render items
|
|
*/
|
|
for (i = 0; i < self->opaque_render_items->len; i++)
|
|
{
|
|
RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i);
|
|
GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node);
|
|
|
|
if (changes == 0)
|
|
continue;
|
|
|
|
if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0)
|
|
{
|
|
item->opaque = gsk_render_node_is_opaque (item->node);
|
|
item->opacity = gsk_render_node_get_opacity (item->node);
|
|
changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY;
|
|
}
|
|
|
|
if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM)
|
|
{
|
|
gsk_render_node_get_world_matrix (item->node, &item->mvp);
|
|
changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
|
|
}
|
|
|
|
if (changes != 0)
|
|
{
|
|
clear_items = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < self->transparent_render_items->len; i++)
|
|
{
|
|
RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i);
|
|
GskRenderNodeChanges changes = gsk_render_node_get_last_state (item->node);
|
|
|
|
if (changes == 0)
|
|
continue;
|
|
|
|
if ((changes & GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY) != 0)
|
|
{
|
|
item->opaque = gsk_render_node_is_opaque (item->node);
|
|
item->opacity = gsk_render_node_get_opacity (item->node);
|
|
changes &= ~GSK_RENDER_NODE_CHANGES_UPDATE_OPACITY;
|
|
}
|
|
|
|
if (changes & GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM)
|
|
{
|
|
gsk_render_node_get_world_matrix (item->node, &item->mvp);
|
|
changes &= ~ GSK_RENDER_NODE_CHANGES_UPDATE_TRANSFORM;
|
|
}
|
|
|
|
if (changes != 0)
|
|
{
|
|
clear_items = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
clear_items = TRUE;
|
|
|
|
if (!clear_items)
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("Tree is still valid\n"));
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < self->opaque_render_items->len; i++)
|
|
render_item_clear (&g_array_index (self->opaque_render_items, RenderItem, i));
|
|
for (i = 0; i < self->transparent_render_items->len; i++)
|
|
render_item_clear (&g_array_index (self->transparent_render_items, RenderItem, i));
|
|
|
|
g_array_set_size (self->opaque_render_items, 0);
|
|
g_array_set_size (self->transparent_render_items, 0);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n"));
|
|
gsk_gl_renderer_add_render_item (self, gsk_renderer_get_root_node (renderer));
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Sorting render nodes\n"));
|
|
g_array_sort (self->opaque_render_items, opaque_item_cmp);
|
|
g_array_sort (self->transparent_render_items, transparent_item_cmp);
|
|
|
|
out:
|
|
GSK_NOTE (OPENGL, g_print ("Total render items: %d (opaque:%d, transparent:%d)\n",
|
|
self->opaque_render_items->len + self->transparent_render_items->len,
|
|
self->opaque_render_items->len,
|
|
self->transparent_render_items->len));
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_clear (GskRenderer *renderer)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_render (GskRenderer *renderer)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
graphene_rect_t viewport;
|
|
int scale, status, clear_bits;
|
|
guint i;
|
|
|
|
if (self->context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
|
|
gsk_renderer_get_viewport (renderer, &viewport);
|
|
|
|
gsk_gl_renderer_create_buffers (self);
|
|
gsk_gl_renderer_allocate_buffers (self, viewport.size.width, viewport.size.height);
|
|
gsk_gl_renderer_attach_buffers (self);
|
|
|
|
if (self->has_depth_buffer)
|
|
glEnable (GL_DEPTH_TEST);
|
|
else
|
|
glDisable (GL_DEPTH_TEST);
|
|
|
|
/* Ensure that the viewport is up to date */
|
|
status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
|
|
if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %g, %g)\n",
|
|
viewport.size.width,
|
|
viewport.size.height));
|
|
glViewport (0, 0, viewport.size.width, viewport.size.height);
|
|
}
|
|
|
|
clear_bits = GL_COLOR_BUFFER_BIT;
|
|
if (self->has_depth_buffer)
|
|
clear_bits |= GL_DEPTH_BUFFER_BIT;
|
|
if (self->has_stencil_buffer)
|
|
clear_bits |= GL_STENCIL_BUFFER_BIT;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
|
|
glClearColor (0, 0, 0, 0);
|
|
glClear (clear_bits);
|
|
|
|
/* Opaque pass: front-to-back */
|
|
GSK_NOTE (OPENGL, g_print ("Rendering %u opaque items\n", self->opaque_render_items->len));
|
|
for (i = 0; i < self->opaque_render_items->len; i++)
|
|
{
|
|
RenderItem *item = &g_array_index (self->opaque_render_items, RenderItem, i);
|
|
|
|
render_item (item);
|
|
}
|
|
|
|
glEnable (GL_BLEND);
|
|
|
|
/* Transparent pass: back-to-front */
|
|
GSK_NOTE (OPENGL, g_print ("Rendering %u transparent items\n", self->transparent_render_items->len));
|
|
for (i = 0; i < self->transparent_render_items->len; i++)
|
|
{
|
|
RenderItem *item = &g_array_index (self->transparent_render_items, RenderItem, i);
|
|
|
|
render_item (item);
|
|
}
|
|
|
|
glDisable (GL_BLEND);
|
|
|
|
/* Draw the output of the GL rendering to the window */
|
|
GSK_NOTE (OPENGL, g_print ("Drawing GL content on Cairo surface using a %s\n",
|
|
self->texture_id != 0 ? "texture" : "renderbuffer"));
|
|
scale = 1;
|
|
gdk_cairo_draw_from_gl (gsk_renderer_get_draw_context (renderer),
|
|
gsk_renderer_get_window (renderer),
|
|
self->texture_id != 0 ? self->texture_id : self->render_buffer,
|
|
self->texture_id != 0 ? GL_TEXTURE : GL_RENDERBUFFER,
|
|
scale,
|
|
0, 0, viewport.size.width, viewport.size.height);
|
|
|
|
gdk_gl_context_make_current (self->context);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_class_init (GskGLRendererClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
|
|
|
|
gobject_class->dispose = gsk_gl_renderer_dispose;
|
|
|
|
renderer_class->realize = gsk_gl_renderer_realize;
|
|
renderer_class->unrealize = gsk_gl_renderer_unrealize;
|
|
renderer_class->resize_viewport = gsk_gl_renderer_resize_viewport;
|
|
renderer_class->update = gsk_gl_renderer_update;
|
|
renderer_class->clear = gsk_gl_renderer_clear;
|
|
renderer_class->validate_tree = gsk_gl_renderer_validate_tree;
|
|
renderer_class->render = gsk_gl_renderer_render;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_init (GskGLRenderer *self)
|
|
{
|
|
gsk_ensure_resources ();
|
|
|
|
graphene_matrix_init_identity (&self->mvp);
|
|
|
|
self->has_depth_buffer = TRUE;
|
|
self->has_stencil_buffer = TRUE;
|
|
}
|
|
|
|
void
|
|
gsk_gl_renderer_set_context (GskGLRenderer *renderer,
|
|
GdkGLContext *context)
|
|
{
|
|
g_return_if_fail (GSK_IS_GL_RENDERER (renderer));
|
|
g_return_if_fail (context == NULL || GDK_IS_GL_CONTEXT (context));
|
|
|
|
if (gsk_renderer_is_realized (GSK_RENDERER (renderer)))
|
|
return;
|
|
|
|
if (gdk_gl_context_get_display (context) != gsk_renderer_get_display (GSK_RENDERER (renderer)))
|
|
return;
|
|
|
|
g_set_object (&renderer->context, context);
|
|
}
|
|
|
|
GdkGLContext *
|
|
gsk_gl_renderer_get_context (GskGLRenderer *renderer)
|
|
{
|
|
g_return_val_if_fail (GSK_IS_GL_RENDERER (renderer), NULL);
|
|
|
|
return renderer->context;
|
|
}
|