#include "config.h" #include "gskglrendererprivate.h" #include "gskdebugprivate.h" #include "gskenums.h" #include "gskgldriverprivate.h" #include "gskglprofilerprivate.h" #include "gskprofilerprivate.h" #include "gskrendererprivate.h" #include "gskrendernodeprivate.h" #include "gskshaderbuilderprivate.h" #include "gsktextureprivate.h" #include "gskprivate.h" #include #define SHADER_VERSION_GLES 100 #define SHADER_VERSION_GL2_LEGACY 110 #define SHADER_VERSION_GL3_LEGACY 130 #define SHADER_VERSION_GL3 150 typedef struct { int id; /* Common locations (gl_common)*/ int mvp_location; int source_location; int mask_location; int uv_location; int position_location; int alpha_location; int blendMode_location; /* Shader-specific locations */ union { struct { int color_location; }; }; } Program; typedef struct { int render_target_id; int vao_id; int buffer_id; int texture_id; int program_id; Program *program; } RenderData; enum { MODE_COLOR = 1, MODE_TEXTURE, N_MODES }; typedef struct { int mode; /* 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; float opacity; float z; union { struct { GdkRGBA color; } color_data; struct { int a,b; } texture_data; }; const char *name; GskBlendMode blend_mode; RenderData render_data; RenderData *parent_data; GArray *children; } RenderItem; enum { MVP, SOURCE, MASK, ALPHA, BLEND_MODE, N_UNIFORMS }; enum { POSITION, UV, N_ATTRIBUTES }; #ifdef G_ENABLE_DEBUG typedef struct { GQuark frames; GQuark draw_calls; } ProfileCounters; typedef struct { GQuark cpu_time; GQuark gpu_time; } ProfileTimers; #endif typedef enum { RENDER_FULL, RENDER_SCISSOR } RenderMode; #define NUM_PROGRAMS 3 struct _GskGLRenderer { GskRenderer parent_instance; graphene_matrix_t mvp; graphene_frustum_t frustum; guint frame_buffer; guint depth_stencil_buffer; guint texture_id; GQuark uniforms[N_UNIFORMS]; GQuark attributes[N_ATTRIBUTES]; GdkGLContext *gl_context; GskGLDriver *gl_driver; GskGLProfiler *gl_profiler; GskShaderBuilder *shader_builder; union { struct { Program blend_program; Program blit_program; Program color_program; }; struct { Program programs[NUM_PROGRAMS]; }; }; GArray *render_items; #ifdef G_ENABLE_DEBUG ProfileCounters profile_counters; ProfileTimers profile_timers; #endif RenderMode render_mode; gboolean has_buffers : 1; }; struct _GskGLRendererClass { GskRendererClass parent_class; }; 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->gl_context); g_clear_pointer (&self->render_items, g_array_unref); G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject); } static void gsk_gl_renderer_create_buffers (GskGLRenderer *self, int width, int height, int scale_factor) { if (self->has_buffers) return; GSK_NOTE (OPENGL, g_print ("Creating buffers (w:%d, h:%d, scale:%d)\n", width, height, scale_factor)); if (self->texture_id == 0) { self->texture_id = gsk_gl_driver_create_texture (self->gl_driver, width * scale_factor, height * scale_factor); gsk_gl_driver_bind_source_texture (self->gl_driver, self->texture_id); gsk_gl_driver_init_texture_empty (self->gl_driver, self->texture_id); } gsk_gl_driver_create_render_target (self->gl_driver, self->texture_id, TRUE, TRUE); self->has_buffers = TRUE; } static void gsk_gl_renderer_destroy_buffers (GskGLRenderer *self) { if (self->gl_context == NULL) return; if (!self->has_buffers) return; GSK_NOTE (OPENGL, g_print ("Destroying buffers\n")); gdk_gl_context_make_current (self->gl_context); if (self->texture_id != 0) { gsk_gl_driver_destroy_texture (self->gl_driver, self->texture_id); self->texture_id = 0; } self->has_buffers = FALSE; } static void init_common_locations (GskGLRenderer *self, Program *prog) { prog->source_location = gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[SOURCE]); prog->mask_location = gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MASK]); prog->mvp_location = gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MVP]); prog->alpha_location = gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[ALPHA]); prog->blendMode_location = gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[BLEND_MODE]); prog->position_location = gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[POSITION]); prog->uv_location = gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[UV]); } static gboolean gsk_gl_renderer_create_programs (GskGLRenderer *self, GError **error) { GskShaderBuilder *builder; GError *shader_error = NULL; gboolean res = FALSE; builder = gsk_shader_builder_new (); gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl"); self->uniforms[MVP] = gsk_shader_builder_add_uniform (builder, "uMVP"); self->uniforms[SOURCE] = gsk_shader_builder_add_uniform (builder, "uSource"); self->uniforms[MASK] = gsk_shader_builder_add_uniform (builder, "uMask"); self->uniforms[ALPHA] = gsk_shader_builder_add_uniform (builder, "uAlpha"); self->uniforms[BLEND_MODE] = gsk_shader_builder_add_uniform (builder, "uBlendMode"); self->attributes[POSITION] = gsk_shader_builder_add_attribute (builder, "aPosition"); self->attributes[UV] = gsk_shader_builder_add_attribute (builder, "aUv"); if (gdk_gl_context_get_use_es (self->gl_context)) { gsk_shader_builder_set_version (builder, SHADER_VERSION_GLES); gsk_shader_builder_set_vertex_preamble (builder, "es2_common.vs.glsl"); gsk_shader_builder_set_fragment_preamble (builder, "es2_common.fs.glsl"); gsk_shader_builder_add_define (builder, "GSK_GLES", "1"); } else if (gdk_gl_context_is_legacy (self->gl_context)) { int maj, min; gdk_gl_context_get_version (self->gl_context, &maj, &min); if (maj == 3) gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3_LEGACY); else gsk_shader_builder_set_version (builder, SHADER_VERSION_GL2_LEGACY); gsk_shader_builder_set_vertex_preamble (builder, "gl_common.vs.glsl"); gsk_shader_builder_set_fragment_preamble (builder, "gl_common.fs.glsl"); gsk_shader_builder_add_define (builder, "GSK_LEGACY", "1"); } else { gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3); gsk_shader_builder_set_vertex_preamble (builder, "gl3_common.vs.glsl"); gsk_shader_builder_set_fragment_preamble (builder, "gl3_common.fs.glsl"); gsk_shader_builder_add_define (builder, "GSK_GL3", "1"); } #ifdef G_ENABLE_DEBUG if (GSK_RENDER_MODE_CHECK (SHADERS)) gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1"); #endif /* Keep a pointer to query for the uniform and attribute locations * when rendering the scene */ self->shader_builder = builder; self->blend_program.id = gsk_shader_builder_create_program (builder, "blend.vs.glsl", "blend.fs.glsl", &shader_error); if (shader_error != NULL) { g_propagate_prefixed_error (error, shader_error, "Unable to create 'blend' program: "); g_object_unref (builder); goto out; } init_common_locations (self, &self->blend_program); self->blit_program.id = gsk_shader_builder_create_program (builder, "blit.vs.glsl", "blit.fs.glsl", &shader_error); if (shader_error != NULL) { g_propagate_prefixed_error (error, shader_error, "Unable to create 'blit' program: "); g_object_unref (builder); goto out; } init_common_locations (self, &self->blit_program); self->color_program.id = gsk_shader_builder_create_program (builder, "color.vs.glsl", "color.fs.glsl", &shader_error); if (shader_error != NULL) { g_propagate_prefixed_error (error, shader_error, "Unable to create 'color' program: "); g_object_unref (builder); goto out; } init_common_locations (self, &self->color_program); self->color_program.color_location = gsk_shader_builder_get_uniform_location (self->shader_builder, self->color_program.id, g_quark_from_string("uColor")); self->color_program.color_location = glGetUniformLocation(self->color_program.id, "uColor"); g_assert(self->color_program.color_location >= 0); res = TRUE; out: return res; } static void gsk_gl_renderer_destroy_programs (GskGLRenderer *self) { g_clear_object (&self->shader_builder); } static gboolean gsk_gl_renderer_realize (GskRenderer *renderer, GdkWindow *window, GError **error) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); /* If we didn't get a GdkGLContext before realization, try creating * one now, for our exclusive use. */ if (self->gl_context == NULL) { self->gl_context = gdk_window_create_gl_context (window, error); if (self->gl_context == NULL) return FALSE; } if (!gdk_gl_context_realize (self->gl_context, error)) return FALSE; gdk_gl_context_make_current (self->gl_context); g_assert (self->gl_driver == NULL); self->gl_driver = gsk_gl_driver_new (self->gl_context); self->gl_profiler = gsk_gl_profiler_new (self->gl_context); GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n")); if (!gsk_gl_renderer_create_programs (self, error)) return FALSE; return TRUE; } static void gsk_gl_renderer_unrealize (GskRenderer *renderer) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); if (self->gl_context == NULL) return; gdk_gl_context_make_current (self->gl_context); /* We don't need to iterate to destroy the associated GL resources, * as they will be dropped when we finalize the GskGLDriver */ g_clear_pointer (&self->render_items, g_array_unref); gsk_gl_renderer_destroy_buffers (self); gsk_gl_renderer_destroy_programs (self); g_clear_object (&self->gl_profiler); g_clear_object (&self->gl_driver); if (self->gl_context == gdk_gl_context_get_current ()) gdk_gl_context_clear_current (); } static GdkDrawingContext * gsk_gl_renderer_begin_draw_frame (GskRenderer *renderer, const cairo_region_t *update_area) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); cairo_region_t *damage; GdkDrawingContext *result; GdkRectangle whole_window; GdkWindow *window; window = gsk_renderer_get_window (renderer); whole_window = (GdkRectangle) { 0, 0, gdk_window_get_width (window), gdk_window_get_height (window) }; damage = gdk_gl_context_get_damage (self->gl_context); cairo_region_union (damage, update_area); if (cairo_region_contains_rectangle (damage, &whole_window) == CAIRO_REGION_OVERLAP_IN) { self->render_mode = RENDER_FULL; } else { GdkRectangle extents; cairo_region_get_extents (damage, &extents); cairo_region_union_rectangle (damage, &extents); if (gdk_rectangle_equal (&extents, &whole_window)) self->render_mode = RENDER_FULL; else self->render_mode = RENDER_SCISSOR; } result = gdk_window_begin_draw_frame (window, GDK_DRAW_CONTEXT (self->gl_context), damage); cairo_region_destroy (damage); return result; } static void gsk_gl_renderer_resize_viewport (GskGLRenderer *self, const graphene_rect_t *viewport, int scale_factor) { int width = viewport->size.width * scale_factor; int height = viewport->size.height * scale_factor; GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %d, %d) [scale:%d]\n", width, height, scale_factor)); glViewport (0, 0, width, height); } static void gsk_gl_renderer_update_frustum (GskGLRenderer *self, const graphene_matrix_t *modelview, const graphene_matrix_t *projection) { GSK_NOTE (TRANSFORMS, g_print ("Updating the modelview/projection\n")); graphene_matrix_multiply (modelview, projection, &self->mvp); graphene_frustum_init_from_matrix (&self->frustum, &self->mvp); GSK_NOTE (TRANSFORMS, g_print ("Renderer MVP:\n"); graphene_matrix_print (&self->mvp); g_print ("\n")); } #define N_VERTICES 6 static void render_item (GskGLRenderer *self, RenderItem *item) { float mvp[16]; float opacity; if (item->children != NULL) { if (gsk_gl_driver_bind_render_target (self->gl_driver, item->render_data.render_target_id)) { glViewport (0, 0, item->size.width, item->size.height); glClearColor (0.0, 0.0, 0.0, 0.0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } } gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id); glUseProgram (item->render_data.program->id); switch(item->mode) { case MODE_COLOR: { glUniform4f (item->render_data.program->color_location, item->color_data.color.red, item->color_data.color.green, item->color_data.color.blue, item->color_data.color.alpha); } break; case MODE_TEXTURE: { g_assert(item->render_data.texture_id != 0); /* Use texture unit 0 for the source */ glUniform1i (item->render_data.program->source_location, 0); gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.texture_id); if (item->parent_data != NULL) { glUniform1i (item->render_data.program->blendMode_location, item->blend_mode); /* Use texture unit 1 for the mask */ if (item->parent_data->texture_id != 0) { glUniform1i (item->render_data.program->mask_location, 1); gsk_gl_driver_bind_mask_texture (self->gl_driver, item->parent_data->texture_id); } } } break; default: g_assert_not_reached (); } /* Pass the opacity component */ if (item->children != NULL) opacity = 1.0; else opacity = item->opacity; glUniform1f (item->render_data.program->alpha_location, opacity); /* Pass the mvp to the vertex shader */ GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp)); graphene_matrix_to_float (&item->mvp, mvp); glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp); /* Draw the quad */ GSK_NOTE2 (OPENGL, TRANSFORMS, g_print ("Drawing item <%s>[%p] (w:%g, h:%g) with opacity: %g blend mode: %d\n", item->name, item, item->size.width, item->size.height, item->opacity, item->blend_mode)); glDrawArrays (GL_TRIANGLES, 0, N_VERTICES); #ifdef G_ENABLE_DEBUG gsk_profiler_counter_inc (gsk_renderer_get_profiler (GSK_RENDERER (self)), self->profile_counters.draw_calls); #endif /* Render all children items, so we can take the result * render target texture during the compositing */ if (item->children != NULL) { int i; for (i = 0; i < item->children->len; i++) { RenderItem *child = &g_array_index (item->children, RenderItem, i); render_item (self, child); } /* Bind the parent render target */ if (item->parent_data != NULL) gsk_gl_driver_bind_render_target (self->gl_driver, item->parent_data->render_target_id); /* Bind the same VAO, as the render target is created with the same size * and vertices as the texture target */ gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id); /* Since we're rendering the target texture, we only need the blit program */ glUseProgram (self->blit_program.id); /* Use texture unit 0 for the render target */ glUniform1i (item->render_data.program->source_location, 0); gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.render_target_id); /* Pass the opacity component; if we got here, we know that the original render * target is neither fully opaque nor at full opacity */ glUniform1f (item->render_data.program->alpha_location, item->opacity); /* Pass the mvp to the vertex shader */ GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp)); graphene_matrix_to_float (&item->mvp, mvp); glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp); /* Draw the quad */ GSK_NOTE2 (OPENGL, TRANSFORMS, g_print ("Drawing offscreen item <%s>[%p] (w:%g, h:%g) with opacity: %g\n", item->name, item, item->size.width, item->size.height, item->opacity)); glDrawArrays (GL_TRIANGLES, 0, N_VERTICES); } } static void get_gl_scaling_filters (GskRenderNode *node, int *min_filter_r, int *mag_filter_r) { switch (node->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; default: break; } switch (node->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: default: *mag_filter_r = GL_LINEAR; break; } } #if 0 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); } #endif 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 gboolean render_node_needs_render_target (GskRenderNode *node) { return FALSE; } static void gsk_gl_renderer_add_render_item (GskGLRenderer *self, const graphene_matrix_t *projection, const graphene_matrix_t *modelview, GArray *render_items, GskRenderNode *node, RenderItem *parent) { RenderItem item; RenderItem *ritem = NULL; int program_id; int scale_factor; memset (&item, 0, sizeof (RenderItem)); scale_factor = gsk_renderer_get_scale_factor (GSK_RENDERER (self)); if (scale_factor < 1) scale_factor = 1; item.node = node; item.name = node->name != NULL ? node->name : "unnamed"; /* The texture size */ item.size.width = node->bounds.size.width * scale_factor; item.size.height = node->bounds.size.height * scale_factor; /* Each render item is an axis-aligned bounding box that we * transform using the given transformation matrix */ item.min.x = node->bounds.origin.x; item.min.y = node->bounds.origin.y; item.min.z = 0.f; item.max.x = item.min.x + node->bounds.size.width; item.max.y = item.min.y + node->bounds.size.height; item.max.z = 0.f; /* The location of the item, in normalized world coordinates */ graphene_matrix_multiply (modelview, &self->mvp, &item.mvp); item.z = project_item (projection, modelview); item.opacity = 1.0; item.blend_mode = GSK_BLEND_MODE_DEFAULT; /* Back-pointer to the parent node */ if (parent != NULL) item.parent_data = &(parent->render_data); else item.parent_data = NULL; /* Select the program to use */ if (parent != NULL) { item.render_data.program = &self->blend_program; program_id = self->blend_program.id; } else { item.render_data.program = &self->blit_program; program_id = self->blit_program.id; } if (render_node_needs_render_target (node)) { item.render_data.render_target_id = gsk_gl_driver_create_texture (self->gl_driver, item.size.width, item.size.height); gsk_gl_driver_init_texture_empty (self->gl_driver, item.render_data.render_target_id); gsk_gl_driver_create_render_target (self->gl_driver, item.render_data.render_target_id, TRUE, TRUE); item.children = g_array_new (FALSE, FALSE, sizeof (RenderItem)); } else { item.render_data.render_target_id = self->texture_id; item.children = NULL; } switch (gsk_render_node_get_node_type (node)) { case GSK_TEXTURE_NODE: { GskTexture *texture = gsk_texture_node_get_texture (node); int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST; get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter); item.render_data.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver, texture, gl_min_filter, gl_mag_filter); item.mode = MODE_TEXTURE; } break; case GSK_CAIRO_NODE: { const cairo_surface_t *surface = gsk_cairo_node_peek_surface (node); int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST; if (surface == NULL) return; get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter); /* Upload the Cairo surface to a GL texture */ item.render_data.texture_id = gsk_gl_driver_create_texture (self->gl_driver, item.size.width, item.size.height); gsk_gl_driver_bind_source_texture (self->gl_driver, item.render_data.texture_id); gsk_gl_driver_init_texture_with_surface (self->gl_driver, item.render_data.texture_id, (cairo_surface_t *)surface, gl_min_filter, gl_mag_filter); item.mode = MODE_TEXTURE; } break; case GSK_COLOR_NODE: { const GdkRGBA *c = gsk_color_node_peek_color(node); program_id = self->color_program.id; item.render_data.program = &self->color_program; item.mode = MODE_COLOR; item.color_data.color= *c; } break; case GSK_COLOR_MATRIX_NODE: { GskRenderNode *child = gsk_color_matrix_node_get_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } return; case GSK_SHADOW_NODE: { GskRenderNode *child = gsk_shadow_node_get_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } return; case GSK_REPEAT_NODE: { GskRenderNode *child = gsk_repeat_node_get_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } return; case GSK_BLEND_NODE: { GskRenderNode *child = gsk_blend_node_get_bottom_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); child = gsk_blend_node_get_top_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } return; case GSK_CROSS_FADE_NODE: { GskRenderNode *child = gsk_cross_fade_node_get_start_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); child = gsk_cross_fade_node_get_end_child (node); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } return; case GSK_CONTAINER_NODE: { guint i, p; for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i++) { GskRenderNode *child = gsk_container_node_get_child (node, i); gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem); } } return; case GSK_TRANSFORM_NODE: { graphene_matrix_t transform, transformed_mv; graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node)); graphene_matrix_multiply (&transform, modelview, &transformed_mv); gsk_gl_renderer_add_render_item (self, projection, &transformed_mv, render_items, gsk_transform_node_get_child (node), ritem); } return; case GSK_NOT_A_RENDER_NODE: g_assert_not_reached (); return; case GSK_LINEAR_GRADIENT_NODE: case GSK_REPEATING_LINEAR_GRADIENT_NODE: case GSK_BORDER_NODE: case GSK_INSET_SHADOW_NODE: case GSK_OUTSET_SHADOW_NODE: case GSK_OPACITY_NODE: case GSK_CLIP_NODE: case GSK_ROUNDED_CLIP_NODE: case GSK_TEXT_NODE: case GSK_BLUR_NODE: default: { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, item.size.width, item.size.height); cairo_surface_set_device_scale (surface, scale_factor, scale_factor); cr = cairo_create (surface); cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y); gsk_render_node_draw (node, cr); cairo_destroy (cr); /* Upload the Cairo surface to a GL texture */ item.render_data.texture_id = gsk_gl_driver_create_texture (self->gl_driver, item.size.width, item.size.height); gsk_gl_driver_bind_source_texture (self->gl_driver, item.render_data.texture_id); gsk_gl_driver_init_texture_with_surface (self->gl_driver, item.render_data.texture_id, surface, GL_NEAREST, GL_NEAREST); cairo_surface_destroy (surface); item.mode = MODE_TEXTURE; } break; } item.render_data.program_id = program_id; /* Create the vertex buffers holding the geometry of the quad */ { GskQuadVertex vertex_data[N_VERTICES] = { { { 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 }, }, }; item.render_data.vao_id = gsk_gl_driver_create_vao_for_quad (self->gl_driver, item.render_data.program->position_location, item.render_data.program->uv_location, N_VERTICES, vertex_data); } GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n", node->name != NULL ? node->name : "unnamed", node)); g_array_append_val (render_items, item); ritem = &g_array_index (render_items, RenderItem, render_items->len - 1); if (item.children != NULL) render_items = item.children; } static gboolean gsk_gl_renderer_validate_tree (GskGLRenderer *self, GskRenderNode *root, const graphene_matrix_t *projection) { graphene_matrix_t identity; if (self->gl_context == NULL) { GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer")); return FALSE; } graphene_matrix_init_identity (&identity); gdk_gl_context_make_current (self->gl_context); gsk_gl_driver_begin_frame (self->gl_driver); GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n")); gsk_gl_renderer_add_render_item (self, projection, &identity, self->render_items, root, NULL); GSK_NOTE (OPENGL, g_print ("Total render items: %d\n", self->render_items->len)); gsk_gl_driver_end_frame (self->gl_driver); return TRUE; } static void gsk_gl_renderer_clear_tree (GskGLRenderer *self) { int removed_textures, removed_vaos; if (self->gl_context == NULL) return; gdk_gl_context_make_current (self->gl_context); g_array_remove_range (self->render_items, 0, self->render_items->len); removed_textures = gsk_gl_driver_collect_textures (self->gl_driver); removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver); GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n", removed_textures, removed_vaos)); } static void gsk_gl_renderer_clear (GskGLRenderer *self) { GSK_NOTE (OPENGL, g_print ("Clearing viewport\n")); glClearColor (0, 0, 0, 0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } static void gsk_gl_renderer_setup_render_mode (GskGLRenderer *self) { switch (self->render_mode) { case RENDER_FULL: glDisable (GL_SCISSOR_TEST); break; case RENDER_SCISSOR: { GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self)); GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self)); GdkRectangle extents; int scale_factor = gsk_renderer_get_scale_factor (GSK_RENDERER (self)); cairo_region_get_extents (gdk_drawing_context_get_clip (context), &extents); glScissor (extents.x * scale_factor, (gdk_window_get_height (window) - extents.height - extents.y) * scale_factor, extents.width * scale_factor, extents.height * scale_factor); glEnable (GL_SCISSOR_TEST); break; } default: g_assert_not_reached (); break; } } #define ORTHO_NEAR_PLANE -10000 #define ORTHO_FAR_PLANE 10000 static void gsk_gl_renderer_do_render (GskRenderer *renderer, GskRenderNode *root, const graphene_rect_t *viewport, int scale_factor) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); graphene_matrix_t modelview, projection; guint i; #ifdef G_ENABLE_DEBUG GskProfiler *profiler; gint64 gpu_time, cpu_time; #endif #ifdef G_ENABLE_DEBUG profiler = gsk_renderer_get_profiler (renderer); #endif /* Set up the modelview and projection matrices to fit our viewport */ graphene_matrix_init_scale (&modelview, scale_factor, scale_factor, 1.0); graphene_matrix_init_ortho (&projection, viewport->origin.x, viewport->origin.x + viewport->size.width * scale_factor, viewport->origin.y + viewport->size.height * scale_factor, viewport->origin.y, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); gsk_gl_renderer_update_frustum (self, &modelview, &projection); if (!gsk_gl_renderer_validate_tree (self, root, &projection)) return; gsk_gl_driver_begin_frame (self->gl_driver); #ifdef G_ENABLE_DEBUG gsk_gl_profiler_begin_gpu_region (self->gl_profiler); gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time); #endif /* Ensure that the viewport is up to date */ if (gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id)) gsk_gl_renderer_resize_viewport (self, viewport, scale_factor); gsk_gl_renderer_setup_render_mode (self); gsk_gl_renderer_clear (self); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); glEnable (GL_BLEND); glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); GSK_NOTE (OPENGL, g_print ("Rendering %u items\n", self->render_items->len)); for (i = 0; i < self->render_items->len; i++) { RenderItem *item = &g_array_index (self->render_items, RenderItem, i); render_item (self, item); } /* Draw the output of the GL rendering to the window */ gsk_gl_driver_end_frame (self->gl_driver); #ifdef G_ENABLE_DEBUG gsk_profiler_counter_inc (profiler, self->profile_counters.frames); cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time); gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time); gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler); gsk_profiler_timer_set (profiler, self->profile_timers.gpu_time, gpu_time); gsk_profiler_push_samples (profiler); #endif } static GskTexture * gsk_gl_renderer_render_texture (GskRenderer *renderer, GskRenderNode *root, const graphene_rect_t *viewport) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); GskTexture *texture; cairo_surface_t *surface; cairo_t *cr; g_return_val_if_fail (self->gl_context != NULL, NULL); self->render_mode = RENDER_FULL; gdk_gl_context_make_current (self->gl_context); gsk_gl_driver_begin_frame (self->gl_driver); gsk_gl_renderer_create_buffers (self, ceilf (viewport->size.width), ceilf (viewport->size.height), 1); gsk_gl_driver_end_frame (self->gl_driver); gsk_gl_renderer_do_render (renderer, root, viewport, 1); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ceilf (viewport->size.width), ceilf (viewport->size.height)); cr = cairo_create (surface); gdk_cairo_draw_from_gl (cr, gsk_renderer_get_window (renderer), self->texture_id, GL_TEXTURE, 1.0, 0, 0, viewport->size.width, viewport->size.height); cairo_destroy (cr); texture = gsk_texture_new_for_surface (surface); cairo_surface_destroy (surface); return texture; } static void gsk_gl_renderer_render (GskRenderer *renderer, GskRenderNode *root) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); graphene_rect_t viewport; int scale_factor; if (self->gl_context == NULL) return; gdk_gl_context_make_current (self->gl_context); gsk_renderer_get_viewport (renderer, &viewport); scale_factor = gsk_renderer_get_scale_factor (renderer); gsk_gl_renderer_do_render (renderer, root, &viewport, scale_factor); gdk_gl_context_make_current (self->gl_context); gsk_gl_renderer_clear_tree (self); gsk_gl_renderer_destroy_buffers (self); } 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->begin_draw_frame = gsk_gl_renderer_begin_draw_frame; renderer_class->render = gsk_gl_renderer_render; renderer_class->render_texture = gsk_gl_renderer_render_texture; } static void gsk_gl_renderer_init (GskGLRenderer *self) { gsk_ensure_resources (); graphene_matrix_init_identity (&self->mvp); self->render_items = g_array_new (FALSE, FALSE, sizeof (RenderItem)); #ifdef G_ENABLE_DEBUG { GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self)); self->profile_counters.frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE); self->profile_counters.draw_calls = gsk_profiler_add_counter (profiler, "draws", "glDrawArrays", TRUE); self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE); self->profile_timers.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU time", FALSE, TRUE); } #endif }