#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 "gskglglyphcacheprivate.h" #include "gskglrenderopsprivate.h" #include "gskcairoblurprivate.h" #include "gskglshadowcacheprivate.h" #include "gskglnodesampleprivate.h" #include "gskprivate.h" #include "gdk/gdkgltextureprivate.h" #include #include #define SHADER_VERSION_GLES 100 #define SHADER_VERSION_GL2_LEGACY 110 #define SHADER_VERSION_GL3_LEGACY 130 #define SHADER_VERSION_GL3 150 #define ORTHO_NEAR_PLANE -10000 #define ORTHO_FAR_PLANE 10000 #define HIGHLIGHT_FALLBACK 0 #define DEBUG_OPS 0 #define SHADOW_EXTRA_SIZE 4 #if DEBUG_OPS #define OP_PRINT(format, ...) g_print(format, ## __VA_ARGS__) #else #define OP_PRINT(format, ...) #endif #define INIT_PROGRAM_UNIFORM_LOCATION(program_name, uniform_basename) \ G_STMT_START{\ self->program_name ## _program.program_name.uniform_basename ## _location = \ glGetUniformLocation(self->program_name ## _program.id, "u_" #uniform_basename);\ g_assert_cmpint (self->program_name ## _program.program_name.uniform_basename ## _location, >, -1); \ }G_STMT_END #define INIT_COMMON_UNIFORM_LOCATION(program_ptr, uniform_basename) \ G_STMT_START{\ program_ptr->uniform_basename ## _location = \ glGetUniformLocation(program_ptr->id, "u_" #uniform_basename);\ }G_STMT_END typedef enum { FORCE_OFFSCREEN = 1 << 0, RESET_CLIP = 1 << 1, RESET_OPACITY = 1 << 2 } OffscreenFlags; static void G_GNUC_UNUSED print_render_node_tree (GskRenderNode *root, int level) { #define INDENT 4 const guint type = gsk_render_node_get_node_type (root); guint i; switch (type) { case GSK_CONTAINER_NODE: g_print ("%*s Container\n", level * INDENT, " "); for (i = 0; i < gsk_container_node_get_n_children (root); i++) print_render_node_tree (gsk_container_node_get_child (root, i), level + 1); break; case GSK_OFFSET_NODE: g_print ("%*s Offset\n", level * INDENT, " "); print_render_node_tree (gsk_offset_node_get_child (root), level + 1); break; case GSK_TRANSFORM_NODE: g_print ("%*s Transform\n", level * INDENT, " "); print_render_node_tree (gsk_transform_node_get_child (root), level + 1); break; case GSK_COLOR_MATRIX_NODE: g_print ("%*s Color Matrix\n", level * INDENT, " "); print_render_node_tree (gsk_color_matrix_node_get_child (root), level + 1); break; case GSK_CROSS_FADE_NODE: g_print ("%*s Crossfade(%.2f)\n", level * INDENT, " ", gsk_cross_fade_node_get_progress (root)); print_render_node_tree (gsk_cross_fade_node_get_start_child (root), level + 1); print_render_node_tree (gsk_cross_fade_node_get_end_child (root), level + 1); break; case GSK_TEXT_NODE: g_print ("%*s Text\n", level * INDENT, " "); break; case GSK_SHADOW_NODE: g_print ("%*s Shadow\n", level * INDENT, " "); print_render_node_tree (gsk_shadow_node_get_child (root), level + 1); break; case GSK_TEXTURE_NODE: g_print ("%*s Texture %p\n", level * INDENT, " ", gsk_texture_node_get_texture (root)); break; case GSK_DEBUG_NODE: g_print ("%*s Debug: %s\n", level * INDENT, " ", gsk_debug_node_get_message (root)); print_render_node_tree (gsk_debug_node_get_child (root), level + 1); break; case GSK_CLIP_NODE: g_print ("%*s Clip (%f, %f, %f, %f):\n", level * INDENT, " ", root->bounds.origin.x, root->bounds.origin.y, root->bounds.size.width, root->bounds.size.height); print_render_node_tree (gsk_clip_node_get_child (root), level + 1); break; default: g_print ("%*s %s\n", level * INDENT, " ", root->node_class->type_name); } #undef INDENT } static void G_GNUC_UNUSED dump_framebuffer (const char *filename, int w, int h) { int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w); guchar *data = g_malloc (h * stride); cairo_surface_t *s; glReadPixels (0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, data); s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, w, h, stride); cairo_surface_write_to_png (s, filename); cairo_surface_destroy (s); g_free (data); } static gboolean font_has_color_glyphs (const PangoFont *font) { cairo_scaled_font_t *scaled_font; gboolean has_color = FALSE; scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font); if (cairo_scaled_font_get_type (scaled_font) == CAIRO_FONT_TYPE_FT) { FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font); has_color = (FT_HAS_COLOR (ft_face) != 0); cairo_ft_scaled_font_unlock_face (scaled_font); } return has_color; } static void get_gl_scaling_filters (GskRenderNode *node, int *min_filter_r, int *mag_filter_r) { *min_filter_r = GL_LINEAR; *mag_filter_r = GL_LINEAR; } static inline void rgba_to_float (const GdkRGBA *c, float *f) { f[0] = c->red; f[1] = c->green; f[2] = c->blue; f[3] = c->alpha; } static inline void sort_border_sides (const GdkRGBA *colors, int *indices) { gboolean done[4] = {0, 0, 0, 0}; int i, k; int cur = 0; for (i = 0; i < 3; i ++) { if (done[i]) continue; indices[cur] = i; done[i] = TRUE; cur ++; for (k = i + 1; k < 4; k ++) { if (gdk_rgba_equal (&colors[k], &colors[i])) { indices[cur] = k; done[k] = TRUE; cur ++; } } if (cur >= 4) break; } } static inline gboolean color_matrix_modifies_alpha (GskRenderNode *node) { const graphene_matrix_t *matrix = gsk_color_matrix_node_peek_color_matrix (node); const graphene_vec4_t *offset = gsk_color_matrix_node_peek_color_offset (node); graphene_vec4_t row3; if (graphene_vec4_get_w (offset) != 0.0f) return TRUE; graphene_matrix_get_row (matrix, 3, &row3); return !graphene_vec4_equal (graphene_vec4_w_axis (), &row3); } static inline void gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self) { self->bounds.size.width = ceilf (MAX (MAX (self->corner[0].width, self->corner[1].width), MAX (self->corner[2].width, self->corner[3].width)) * 2); self->bounds.size.height = ceilf (MAX (MAX (self->corner[0].height, self->corner[1].height), MAX (self->corner[2].height, self->corner[3].height)) * 2); } static inline gboolean node_supports_transform (GskRenderNode *node) { /* Some nodes can't handle non-trivial transforms without being * rendered to a texture (e.g. rotated clips, etc.). Some however * work just fine, mostly because they already draw their child * to a texture and just render the texture manipulated in some * way, think opacity or color matrix. */ const guint node_type = gsk_render_node_get_node_type (node); switch (node_type) { case GSK_COLOR_NODE: case GSK_OPACITY_NODE: case GSK_COLOR_MATRIX_NODE: case GSK_TEXTURE_NODE: return TRUE; default: return FALSE; } return FALSE; } static void gsk_gl_renderer_setup_render_mode (GskGLRenderer *self); static void add_offscreen_ops (GskGLRenderer *self, RenderOpBuilder *builder, const graphene_rect_t *bounds, GskRenderNode *child_node, int *texture_id, gboolean *is_offscreen, guint flags); static void gsk_gl_renderer_add_render_ops (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder); struct _GskGLRenderer { GskRenderer parent_instance; int scale_factor; GdkGLContext *gl_context; GskGLDriver *gl_driver; GskGLProfiler *gl_profiler; union { Program programs[GL_N_PROGRAMS]; struct { Program blit_program; Program color_program; Program coloring_program; Program color_matrix_program; Program linear_gradient_program; Program blur_program; Program inset_shadow_program; Program outset_shadow_program; Program unblurred_outset_shadow_program; Program border_program; Program cross_fade_program; }; }; GArray *render_ops; GskGLGlyphCache glyph_cache; GskGLShadowCache shadow_cache; #ifdef G_ENABLE_DEBUG struct { GQuark frames; GQuark draw_calls; } profile_counters; struct { GQuark cpu_time; GQuark gpu_time; } profile_timers; #endif cairo_region_t *render_region; }; struct _GskGLRendererClass { GskRendererClass parent_class; }; G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER) static void G_GNUC_UNUSED add_rect_ops (RenderOpBuilder *builder, const graphene_rect_t *r) { const float min_x = r->origin.x; const float min_y = r->origin.y; const float max_x = min_x + r->size.width; const float max_y = min_y + r->size.height; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }); } static void G_GNUC_UNUSED add_rect_outline_ops (GskGLRenderer *self, RenderOpBuilder *builder, const graphene_rect_t *rect) { ops_set_program (builder, &self->color_program); ops_set_color (builder, &(GdkRGBA) { 1, 0, 0, 1 }); add_rect_ops (builder, &GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y, 1, rect->size.height)); add_rect_ops (builder, &GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y, rect->size.width, 1)); add_rect_ops (builder, &GRAPHENE_RECT_INIT (rect->origin.x + rect->size.width - 1, rect->origin.y, 1, rect->size.height)); add_rect_ops (builder, &GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y + rect->size.height - 1, rect->size.width, 1)); } static inline void rounded_rect_to_floats (GskGLRenderer *self, RenderOpBuilder *builder, const GskRoundedRect *rect, float *outline, float *corner_widths, float *corner_heights) { const float scale = ops_get_scale (builder); int i; graphene_rect_t transformed_bounds; ops_transform_bounds_modelview (builder, &rect->bounds, &transformed_bounds); outline[0] = transformed_bounds.origin.x; outline[1] = transformed_bounds.origin.y; outline[2] = transformed_bounds.size.width; outline[3] = transformed_bounds.size.height; for (i = 0; i < 4; i ++) { corner_widths[i] = rect->corner[i].width * scale; corner_heights[i] = rect->corner[i].height * scale; } } static inline void render_fallback_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { const float scale = ops_get_scale (builder); const int surface_width = ceilf (node->bounds.size.width) * scale; const int surface_height = ceilf (node->bounds.size.height) * scale; cairo_surface_t *surface; cairo_t *cr; int texture_id; if (surface_width <= 0 || surface_height <= 0) return; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, surface_width, surface_height); cairo_surface_set_device_scale (surface, scale, scale); cr = cairo_create (surface); cairo_save (cr); cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y); gsk_render_node_draw (node, cr); cairo_restore (cr); #if HIGHLIGHT_FALLBACK if (gsk_render_node_get_node_type (node) != GSK_CAIRO_NODE) { cairo_move_to (cr, 0, 0); cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height); cairo_set_source_rgba (cr, 1, 0, 0, 1); cairo_stroke (cr); } #endif cairo_destroy (cr); /* Upload the Cairo surface to a GL texture */ texture_id = gsk_gl_driver_create_texture (self->gl_driver, surface_width, surface_height); gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id); gsk_gl_driver_init_texture_with_surface (self->gl_driver, texture_id, surface, GL_NEAREST, GL_NEAREST); cairo_surface_destroy (surface); ops_set_program (builder, &self->blit_program); ops_set_texture (builder, texture_id); ops_draw (builder, vertex_data); } static inline void render_text_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GdkRGBA *color, gboolean force_color) { const PangoFont *font = gsk_text_node_peek_font (node); const PangoGlyphInfo *glyphs = gsk_text_node_peek_glyphs (node); const float text_scale = ops_get_scale (builder); guint num_glyphs = gsk_text_node_get_num_glyphs (node); int i; int x_position = 0; float x = gsk_text_node_get_x (node) + builder->dx; float y = gsk_text_node_get_y (node) + builder->dy; /* If the font has color glyphs, we don't need to recolor anything */ if (!force_color && font_has_color_glyphs (font)) { ops_set_program (builder, &self->blit_program); } else { ops_set_program (builder, &self->coloring_program); ops_set_color (builder, color); } /* We use one quad per character, unlike the other nodes which * use at most one quad altogether */ for (i = 0; i < num_glyphs; i++) { const PangoGlyphInfo *gi = &glyphs[i]; const GskGLCachedGlyph *glyph; float glyph_x, glyph_y, glyph_w, glyph_h; float tx, ty, tx2, ty2; double cx; double cy; if (gi->glyph == PANGO_GLYPH_EMPTY) continue; glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache, TRUE, (PangoFont *)font, gi->glyph, text_scale); /* e.g. whitespace */ if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0) goto next; cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE; cy = (double)(gi->geometry.y_offset) / PANGO_SCALE; ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache, glyph)->texture_id); tx = glyph->tx; ty = glyph->ty; tx2 = tx + glyph->tw; ty2 = ty + glyph->th; glyph_x = x + cx + glyph->draw_x; glyph_y = y + cy + glyph->draw_y; glyph_w = glyph->draw_width; glyph_h = glyph->draw_height; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { glyph_x, glyph_y }, { tx, ty }, }, { { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, }, { { glyph_x + glyph_w, glyph_y }, { tx2, ty }, }, { { glyph_x + glyph_w, glyph_y + glyph_h }, { tx2, ty2 }, }, { { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, }, { { glyph_x + glyph_w, glyph_y }, { tx2, ty }, }, }); next: x_position += gi->geometry.width; } } static inline void render_border_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const float scale = ops_get_scale (builder); const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; const GdkRGBA *colors = gsk_border_node_peek_colors (node); const GskRoundedRect *rounded_outline = gsk_border_node_peek_outline (node); const float *og_widths = gsk_border_node_peek_widths (node); GskRoundedRect outline; float widths[4]; int i; struct { float w; float h; } sizes[4]; for (i = 0; i < 4; i ++) widths[i] = og_widths[i]; /* Top left */ if (widths[3] > 0) sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width); else sizes[0].w = 0; if (widths[0] > 0) sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height); else sizes[0].h = 0; /* Top right */ if (widths[1] > 0) sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width); else sizes[1].w = 0; if (widths[0] > 0) sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height); else sizes[1].h = 0; /* Bottom right */ if (widths[1] > 0) sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width); else sizes[2].w = 0; if (widths[2] > 0) sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height); else sizes[2].h = 0; /* Bottom left */ if (widths[3] > 0) sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width); else sizes[3].w = 0; if (widths[2] > 0) sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height); else sizes[3].h = 0; for (i = 0; i < 4; i ++) widths[i] *= scale; { const GskQuadVertex side_data[4][6] = { /* Top */ { { { min_x, min_y }, { 0, 1 }, }, /* Upper left */ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */ { { max_x - sizes[1].w, min_y + sizes[1].h }, { 1, 0 }, }, /* Lower right */ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */ }, /* Right */ { { { max_x - sizes[1].w, min_y + sizes[1].h }, { 0, 1 }, }, /* Upper left */ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */ { { max_x, max_y }, { 1, 0 }, }, /* Lower right */ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */ }, /* Bottom */ { { { min_x + sizes[3].w, max_y - sizes[3].h }, { 0, 1 }, }, /* Upper left */ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */ { { max_x, max_y }, { 1, 0 }, }, /* Lower right */ { { min_x , max_y }, { 0, 0 }, }, /* Lower left */ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */ }, /* Left */ { { { min_x, min_y }, { 0, 1 }, }, /* Upper left */ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */ { { min_x + sizes[3].w, max_y - sizes[3].h }, { 1, 0 }, }, /* Lower right */ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */ } }; int indices[4] = { 0, 1, 2, 3 }; /* We sort them by color */ sort_border_sides (colors, indices); /* Prepare outline */ outline = *rounded_outline; ops_transform_bounds_modelview (builder, &outline.bounds, &outline.bounds); for (i = 0; i < 4; i ++) { outline.corner[i].width *= scale; outline.corner[i].height *= scale; } ops_set_program (builder, &self->border_program); ops_set_border_width (builder, widths); ops_set_border (builder, &outline); for (i = 0; i < 4; i ++) { if (widths[indices[i]] > 0) { ops_set_border_color (builder, &colors[indices[i]]); ops_draw (builder, side_data[indices[i]]); } } } } static inline void render_color_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { ops_set_program (builder, &self->color_program); ops_set_color (builder, gsk_color_node_peek_color (node)); ops_draw (builder, vertex_data); } static inline void render_texture_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { GdkTexture *texture = gsk_texture_node_get_texture (node); const int max_texture_size = gsk_gl_driver_get_max_texture_size (self->gl_driver); const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; if (texture->width > max_texture_size || texture->height > max_texture_size) { const float scale_x = (max_x - min_x) / texture->width; const float scale_y = (max_y - min_y) / texture->height; TextureSlice *slices; guint n_slices; guint i; gsk_gl_driver_slice_texture (self->gl_driver, texture, &slices, &n_slices); ops_set_program (builder, &self->blit_program); for (i = 0; i < n_slices; i ++) { const TextureSlice *slice = &slices[i]; float x1, x2, y1, y2; x1 = min_x + (scale_x * slice->rect.x); x2 = x1 + (slice->rect.width * scale_x); y1 = min_y + (scale_y * slice->rect.y); y2 = y1 + (slice->rect.height * scale_y); ops_set_texture (builder, slice->texture_id); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { 0, 0 }, }, { { x1, y2 }, { 0, 1 }, }, { { x2, y1 }, { 1, 0 }, }, { { x2, y2 }, { 1, 1 }, }, { { x1, y2 }, { 0, 1 }, }, { { x2, y1 }, { 1, 0 }, }, }); } } else { int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST; int texture_id; get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter); texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver, texture, gl_min_filter, gl_mag_filter); ops_set_program (builder, &self->blit_program); ops_set_texture (builder, texture_id); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { min_x, min_y }, { 0, 0 }, }, { { min_x, max_y }, { 0, 1 }, }, { { max_x, min_y }, { 1, 0 }, }, { { max_x, max_y }, { 1, 1 }, }, { { min_x, max_y }, { 0, 1 }, }, { { max_x, min_y }, { 1, 0 }, }, }); } } static inline void render_offset_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { GskRenderNode *child = gsk_offset_node_get_child (node); const float dx = gsk_offset_node_get_x_offset (node); const float dy = gsk_offset_node_get_y_offset (node); ops_offset (builder, dx, dy); gsk_gl_renderer_add_render_ops (self, child, builder); ops_offset (builder, - dx, - dy); } static inline void render_transform_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const float scale = ops_get_scale (builder); GskRenderNode *child = gsk_transform_node_get_child (node); graphene_matrix_t transform, transformed_mv; graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node)); graphene_matrix_multiply (&transform, builder->current_modelview, &transformed_mv); graphene_matrix_translate (&transformed_mv, &(graphene_point3d_t) { builder->dx * scale, builder->dy * scale, 0}); /* We just added the offset to the new modelview matrix, so the following * cases dont' have to care about builder->dx/dy! */ ops_push_modelview (builder, &transformed_mv); if (ops_modelview_is_simple (builder) || node_supports_transform (child)) { const float dx = builder->dx; const float dy = builder->dy; builder->dx = 0; builder->dy = 0; gsk_gl_renderer_add_render_ops (self, child, builder); builder->dx = dx; builder->dy = dy; } else { const float min_x = node->bounds.origin.x; const float min_y = node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; const GskQuadVertex vertex_data[GL_N_VERTICES] = { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }; int texture_id; gboolean is_offscreen; /* For non-trivial transforms, we draw everything on a texture and then * draw the texture transformed. */ /* TODO: We should compute a modelview containing only the "non-trivial" * part (e.g. the rotation) and use that. We want to keep the scale * for the texture. */ add_offscreen_ops (self, builder, &node->bounds, child, &texture_id, &is_offscreen, RESET_CLIP | RESET_OPACITY); ops_set_texture (builder, texture_id); ops_set_program (builder, &self->blit_program); ops_draw (builder, vertex_data); } ops_pop_modelview (builder); } static inline void render_opacity_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { float prev_opacity; prev_opacity = ops_set_opacity (builder, builder->current_opacity * gsk_opacity_node_get_opacity (node)); gsk_gl_renderer_add_render_ops (self, gsk_opacity_node_get_child (node), builder); ops_set_opacity (builder, prev_opacity); } static inline void render_linear_gradient_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { RenderOp op; int n_color_stops = MIN (8, gsk_linear_gradient_node_get_n_color_stops (node)); const GskColorStop *stops = gsk_linear_gradient_node_peek_color_stops (node); const graphene_point_t *start = gsk_linear_gradient_node_peek_start (node); const graphene_point_t *end = gsk_linear_gradient_node_peek_end (node); int i; for (i = 0; i < n_color_stops; i ++) { const GskColorStop *stop = stops + i; op.linear_gradient.color_stops[(i * 4) + 0] = stop->color.red; op.linear_gradient.color_stops[(i * 4) + 1] = stop->color.green; op.linear_gradient.color_stops[(i * 4) + 2] = stop->color.blue; op.linear_gradient.color_stops[(i * 4) + 3] = stop->color.alpha; op.linear_gradient.color_offsets[i] = stop->offset; } ops_set_program (builder, &self->linear_gradient_program); op.op = OP_CHANGE_LINEAR_GRADIENT; op.linear_gradient.n_color_stops = n_color_stops; op.linear_gradient.start_point = *start; op.linear_gradient.start_point.x += builder->dx; op.linear_gradient.start_point.y += builder->dy; op.linear_gradient.end_point = *end; op.linear_gradient.end_point.x += builder->dx; op.linear_gradient.end_point.y += builder->dy; ops_add (builder, &op); ops_draw (builder, vertex_data); } static inline void render_clip_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { GskRenderNode *child = gsk_clip_node_get_child (node); graphene_rect_t transformed_clip; graphene_rect_t intersection; GskRoundedRect child_clip; transformed_clip = *gsk_clip_node_peek_clip (node); ops_transform_bounds_modelview (builder, &transformed_clip, &transformed_clip); graphene_rect_intersection (&transformed_clip, &builder->current_clip->bounds, &intersection); gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f); ops_push_clip (builder, &child_clip); gsk_gl_renderer_add_render_ops (self, child, builder); ops_pop_clip (builder); } static gboolean gsk_rounded_rect_intersection (const GskRoundedRect *self, const GskRoundedRect *other, GskRoundedRect *out_intersection) { const graphene_rect_t *self_bounds = &self->bounds; const graphene_rect_t *other_bounds = &other->bounds; if (graphene_rect_contains_rect (self_bounds, other_bounds)) { *out_intersection = *other; return TRUE; } /* TODO: There are a few cases here that we can express using a single * rounded rectangle, which are even interesting in every day usage. * For example, a partially scrolled-away rounded rectangle * might just work. */ return FALSE; } static inline void render_rounded_clip_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const float scale = ops_get_scale (builder); GskRoundedRect child_clip = *gsk_rounded_clip_node_peek_clip (node); GskRoundedRect transformed_clip; GskRenderNode *child = gsk_rounded_clip_node_get_child (node); GskRoundedRect intersection; gboolean need_offscreen; int i; transformed_clip = child_clip; ops_transform_bounds_modelview (builder, &child_clip.bounds, &transformed_clip.bounds); if (!ops_has_clip (builder)) { intersection = transformed_clip; need_offscreen = FALSE; } else { need_offscreen = !gsk_rounded_rect_intersection (builder->current_clip, &transformed_clip, &intersection); } if (!need_offscreen) { /* If they don't intersect at all, we can simply set * the new clip and add the render ops */ for (i = 0; i < 4; i ++) { intersection.corner[i].width *= scale; intersection.corner[i].height *= scale; } ops_push_clip (builder, &intersection); gsk_gl_renderer_add_render_ops (self, child, builder); ops_pop_clip (builder); } else { const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; graphene_matrix_t scale_matrix; gboolean is_offscreen; int texture_id; /* NOTE: We are *not* transforming the clip by the current modelview here. * We instead draw the untransformed clip to a texture and then transform * that texture. * * We do, however, apply the scale factor to the child clip of course. */ graphene_matrix_init_scale (&scale_matrix, scale, scale, 1.0f); graphene_matrix_transform_bounds (&scale_matrix, &child_clip.bounds, &child_clip.bounds); /* Increase corner radius size by scale factor */ for (i = 0; i < 4; i ++) { child_clip.corner[i].width *= scale; child_clip.corner[i].height *= scale; } ops_push_clip (builder, &child_clip); add_offscreen_ops (self, builder, &node->bounds, child, &texture_id, &is_offscreen, FORCE_OFFSCREEN | RESET_OPACITY); ops_pop_clip (builder); ops_set_program (builder, &self->blit_program); ops_set_texture (builder, texture_id); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }); } /* Else this node is entirely out of the current clip node and we don't draw it anyway. */ } static inline void render_color_matrix_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; int texture_id; gboolean is_offscreen; /* Pass min_x/max_x/min_y/max_y without builder->dx/dy! */ add_offscreen_ops (self, builder, &node->bounds, gsk_color_matrix_node_get_child (node), &texture_id, &is_offscreen, RESET_CLIP | RESET_OPACITY); ops_set_program (builder, &self->color_matrix_program); ops_set_color_matrix (builder, gsk_color_matrix_node_peek_color_matrix (node), gsk_color_matrix_node_peek_color_offset (node)); ops_set_texture (builder, texture_id); if (is_offscreen) { GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }; ops_draw (builder, offscreen_vertex_data); } else { ops_draw (builder, vertex_data); } } static inline void render_blur_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; int texture_id; gboolean is_offscreen; RenderOp op; /* TODO(perf): We're forcing the child offscreen even if it's a texture * so the resulting offscreen texture is bigger by the gaussian blur factor * (see gsk_blur_node_new), but we didn't have to do that if the blur * shader could handle that situation. */ add_offscreen_ops (self, builder, &node->bounds, gsk_blur_node_get_child (node), &texture_id, &is_offscreen, RESET_CLIP | FORCE_OFFSCREEN | RESET_OPACITY); ops_set_program (builder, &self->blur_program); op.op = OP_CHANGE_BLUR; graphene_size_init_from_size (&op.blur.size, &node->bounds.size); op.blur.radius = gsk_blur_node_get_radius (node); ops_add (builder, &op); ops_set_texture (builder, texture_id); if (is_offscreen) { GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }; ops_draw (builder, offscreen_vertex_data); } else { ops_draw (builder, vertex_data); } } static inline void render_inset_shadow_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { const float scale = ops_get_scale (builder); RenderOp op; /* TODO: Implement blurred inset shadows as well */ if (gsk_inset_shadow_node_get_blur_radius (node) > 0) { render_fallback_node (self, node, builder, vertex_data); return; } op.op = OP_CHANGE_INSET_SHADOW; rgba_to_float (gsk_inset_shadow_node_peek_color (node), op.inset_shadow.color); rounded_rect_to_floats (self, builder, gsk_inset_shadow_node_peek_outline (node), op.inset_shadow.outline, op.inset_shadow.corner_widths, op.inset_shadow.corner_heights); op.inset_shadow.radius = gsk_inset_shadow_node_get_blur_radius (node) * scale; op.inset_shadow.spread = gsk_inset_shadow_node_get_spread (node) * scale; op.inset_shadow.offset[0] = gsk_inset_shadow_node_get_dx (node) * scale; op.inset_shadow.offset[1] = -gsk_inset_shadow_node_get_dy (node) * scale; ops_set_program (builder, &self->inset_shadow_program); ops_add (builder, &op); ops_draw (builder, vertex_data); } static inline void render_unblurred_outset_shadow_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { const float scale = ops_get_scale (builder); const float spread = gsk_outset_shadow_node_get_spread (node); GskRoundedRect r = *gsk_outset_shadow_node_peek_outline (node); RenderOp op; op.op = OP_CHANGE_UNBLURRED_OUTSET_SHADOW; rgba_to_float (gsk_outset_shadow_node_peek_color (node), op.unblurred_outset_shadow.color); gsk_rounded_rect_shrink (&r, -spread, -spread, -spread, -spread); rounded_rect_to_floats (self, builder, &r, op.unblurred_outset_shadow.outline, op.unblurred_outset_shadow.corner_widths, op.unblurred_outset_shadow.corner_heights); op.unblurred_outset_shadow.spread = gsk_outset_shadow_node_get_spread (node) * scale; op.unblurred_outset_shadow.offset[0] = gsk_outset_shadow_node_get_dx (node) * scale; op.unblurred_outset_shadow.offset[1] = -gsk_outset_shadow_node_get_dy (node) * scale; ops_set_program (builder, &self->unblurred_outset_shadow_program); ops_add (builder, &op); ops_draw (builder, vertex_data); } static inline void render_outset_shadow_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const GskRoundedRect *outline = gsk_outset_shadow_node_peek_outline (node); GskRoundedRect offset_outline; const float blur_radius = gsk_outset_shadow_node_get_blur_radius (node); const float blur_extra = gsk_cairo_blur_compute_pixels (blur_radius); const float spread = gsk_outset_shadow_node_get_spread (node); const float dx = gsk_outset_shadow_node_get_dx (node); const float dy = gsk_outset_shadow_node_get_dy (node); const float min_x = builder->dx + outline->bounds.origin.x - spread - blur_extra / 2.0; const float min_y = builder->dy + outline->bounds.origin.y - spread - blur_extra / 2.0; const float max_x = min_x + outline->bounds.size.width + (spread + blur_extra/2.0) * 2; const float max_y = min_y + outline->bounds.size.height + (spread + blur_extra/2.0) * 2; float texture_width, texture_height; RenderOp op; graphene_matrix_t identity; graphene_matrix_t prev_projection; graphene_rect_t prev_viewport; graphene_matrix_t item_proj; int blurred_texture_id; int cached_tid; /* offset_outline is the minimal outline we need to draw the given drop shadow, * enlarged by the spread and offset by the blur radius. */ offset_outline = *outline; /* Shrink our outline to the minimum size that can still hold all the border radii */ gsk_rounded_rect_shrink_to_minimum (&offset_outline); /* Increase by the spread */ gsk_rounded_rect_shrink (&offset_outline, -spread, -spread, -spread, -spread); /* No we need to incorporate the blur radius; since we blur an edge an equal blur_extra/2.0 * on both sides, the minimum side of both width and height needs to be blur_extra */ offset_outline.bounds.size.width = MAX (offset_outline.bounds.size.width, blur_extra); offset_outline.bounds.size.height = MAX (offset_outline.bounds.size.height, blur_extra); /* For the center part, we add a few pixels */ offset_outline.bounds.size.width += SHADOW_EXTRA_SIZE; offset_outline.bounds.size.height += SHADOW_EXTRA_SIZE; offset_outline.bounds.origin.x = blur_extra / 2.0f; offset_outline.bounds.origin.y = blur_extra / 2.0f; texture_width = offset_outline.bounds.size.width + blur_extra; texture_height = offset_outline.bounds.size.height + blur_extra; cached_tid = gsk_gl_shadow_cache_get_texture_id (&self->shadow_cache, self->gl_driver, &offset_outline, blur_radius); if (cached_tid == 0) { int texture_id, render_target; int blurred_render_target; int prev_render_target; GskRoundedRect blit_clip; texture_id = gsk_gl_driver_create_texture (self->gl_driver, texture_width, texture_height); gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id); gsk_gl_driver_init_texture_empty (self->gl_driver, texture_id); render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture_id, FALSE, FALSE); graphene_matrix_init_ortho (&item_proj, 0, texture_width, 0, texture_height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); graphene_matrix_scale (&item_proj, 1, -1, 1); graphene_matrix_init_identity (&identity); prev_render_target = ops_set_render_target (builder, render_target); op.op = OP_CLEAR; ops_add (builder, &op); prev_projection = ops_set_projection (builder, &item_proj); ops_push_modelview (builder, &identity); prev_viewport = ops_set_viewport (builder, &GRAPHENE_RECT_INIT (0, 0, texture_width, texture_height)); /* Draw outline */ ops_set_program (builder, &self->color_program); ops_push_clip (builder, &offset_outline); ops_set_color (builder, gsk_outset_shadow_node_peek_color (node)); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { 0, }, { 0, 1 }, }, { { 0, texture_height }, { 0, 0 }, }, { { texture_width, }, { 1, 1 }, }, { { texture_width, texture_height }, { 1, 0 }, }, { { 0, texture_height }, { 0, 0 }, }, { { texture_width, }, { 1, 1 }, }, }); blurred_texture_id = gsk_gl_driver_create_permanent_texture (self->gl_driver, texture_width, texture_height); gsk_gl_driver_bind_source_texture (self->gl_driver, blurred_texture_id); gsk_gl_driver_init_texture_empty (self->gl_driver, blurred_texture_id); blurred_render_target = gsk_gl_driver_create_render_target (self->gl_driver, blurred_texture_id, TRUE, TRUE); ops_set_render_target (builder, blurred_render_target); ops_pop_clip (builder); op.op = OP_CLEAR; ops_add (builder, &op); gsk_rounded_rect_init_from_rect (&blit_clip, &GRAPHENE_RECT_INIT (0, 0, texture_width, texture_height), 0.0f); ops_set_program (builder, &self->blur_program); op.op = OP_CHANGE_BLUR; op.blur.size.width = texture_width; op.blur.size.height = texture_height; op.blur.radius = blur_radius; ops_add (builder, &op); ops_push_clip (builder, &blit_clip); ops_set_texture (builder, texture_id); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { 0, 0 }, { 0, 1 }, }, { { 0, texture_height }, { 0, 0 }, }, { { texture_width, 0 }, { 1, 1 }, }, { { texture_width, texture_height }, { 1, 0 }, }, { { 0, texture_height }, { 0, 0 }, }, { { texture_width, 0 }, { 1, 1 }, }, }); ops_pop_clip (builder); ops_set_viewport (builder, &prev_viewport); ops_pop_modelview (builder); ops_set_projection (builder, &prev_projection); ops_set_render_target (builder, prev_render_target); gsk_gl_shadow_cache_commit (&self->shadow_cache, &offset_outline, blur_radius, blurred_texture_id); } else { blurred_texture_id = cached_tid; } ops_set_program (builder, &self->outset_shadow_program); ops_set_texture (builder, blurred_texture_id); op.op = OP_CHANGE_OUTSET_SHADOW; rounded_rect_to_floats (self, builder, outline, op.outset_shadow.outline, op.outset_shadow.corner_widths, op.outset_shadow.corner_heights); ops_add (builder, &op); /* We use the one outset shadow op from above to draw all 8 sides/corners. */ { const GskRoundedRect *o = &offset_outline; float top_height = MAX (o->corner[0].height, o->corner[1].height); float bottom_height = MAX (o->corner[2].height, o->corner[3].height); float left_width = MAX (o->corner[0].width, o->corner[3].width); float right_width = MAX (o->corner[1].width, o->corner[2].width); float x1, x2, y1, y2, tx1, tx2, ty1, ty2; top_height = MAX (top_height, blur_extra / 2.0f) + (blur_extra / 2.0f); bottom_height = MAX (bottom_height, blur_extra / 2.0f) + (blur_extra / 2.0f); left_width = MAX (left_width, blur_extra / 2.0f) + (blur_extra / 2.0f); right_width = MAX (right_width, blur_extra / 2.0f) + (blur_extra / 2.0f); /* Top left */ if (top_height > 0 && left_width > 0) { x1 = min_x + dx; x2 = min_x + dx + left_width; y1 = min_y + dy; y2 = min_y + dy + top_height; tx1 = 0; tx2 = left_width / texture_width; ty1 = 1 - (top_height / texture_height); ty2 = 1; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Top right */ if (top_height > 0 && right_width > 0) { x1 = max_x + dx - right_width; x2 = max_x + dx; y1 = min_y + dy; y2 = min_y + dy + top_height; tx1 = 1 - (right_width / texture_width); tx2 = 1; ty1 = 1 - (top_height / texture_height); ty2 = 1; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Bottom right */ if (bottom_height > 0 && left_width > 0) { x1 = max_x + dx - right_width; x2 = max_x + dx; y1 = max_y + dy - bottom_height; y2 = max_y + dy; tx1 = 1 - (right_width / texture_width); tx2 = 1; ty1 = 0; ty2 = (bottom_height / texture_height); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Bottom left */ if (bottom_height > 0 && left_width > 0) { x1 = min_x + dx; x2 = min_x + dx + left_width; y1 = max_y + dy - bottom_height; y2 = max_y + dy; tx1 = 0; tx2 = left_width / texture_width; ty1 = 0; ty2 = bottom_height / texture_height; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Left side */ if (left_width > 0) { x1 = min_x + dx; x2 = min_x + dx + left_width; y1 = min_y + dy + top_height; y2 = max_y + dy - bottom_height; tx1 = 0; tx2 = left_width / texture_width; ty1 = 0.5f - SHADOW_EXTRA_SIZE / 2.0f / texture_height; ty2 = ty1 + (SHADOW_EXTRA_SIZE / texture_height); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Right side */ if (right_width > 0) { x1 = max_x + dx - right_width; x2 = max_x + dx; y1 = min_y + dy + top_height; y2 = max_y + dy - bottom_height; tx1 = 1 - (right_width / texture_width); tx2 = 1; ty1 = 0.5f - SHADOW_EXTRA_SIZE / 2.0f / texture_height; ty2 = ty1 + (SHADOW_EXTRA_SIZE / texture_height); ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Top side */ if (top_height > 0) { x1 = min_x + dx + left_width; x2 = max_x + dx - right_width; y1 = min_y + dy; y2 = min_y + dy + top_height; tx1 = 0.5f - (SHADOW_EXTRA_SIZE / 2.0f / texture_width); tx2 = tx1 + (SHADOW_EXTRA_SIZE / texture_width); ty1 = 1 - (top_height / texture_height); ty2 = 1; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Bottom side */ if (bottom_height > 0) { x1 = min_x + dx + left_width; x2 = max_x + dx - right_width; y1 = max_y + dy - bottom_height; y2 = max_y + dy; tx1 = 0.5f - (SHADOW_EXTRA_SIZE / 2.0f / texture_width); tx2 = tx1 + (SHADOW_EXTRA_SIZE / texture_width); ty1 = 0; ty2 = bottom_height / texture_height; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } /* Middle */ x1 = min_x + dx + left_width; x2 = max_x + dx - right_width; y1 = min_y + dy + top_height; y2 = max_y + dy - bottom_height; if (x2 > x1 && y2 > y1) { tx1 = (texture_width - SHADOW_EXTRA_SIZE) / 2.0f / texture_width; tx2 = (texture_width + SHADOW_EXTRA_SIZE) / 2.0f / texture_width; ty1 = (texture_height - SHADOW_EXTRA_SIZE) / 2.0f / texture_height; ty2 = (texture_height + SHADOW_EXTRA_SIZE) / 2.0f / texture_height; ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) { { { x1, y1 }, { tx1, ty2 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, { { x2, y2 }, { tx2, ty1 }, }, { { x1, y2 }, { tx1, ty1 }, }, { { x2, y1 }, { tx2, ty2 }, }, }); } } } static inline void render_shadow_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder, const GskQuadVertex *vertex_data) { float min_x = node->bounds.origin.x; float min_y = node->bounds.origin.y; float max_x = min_x + node->bounds.size.width; float max_y = min_y + node->bounds.size.height; GskRenderNode *original_child = gsk_shadow_node_get_child (node); GskRenderNode *shadow_child = original_child; gsize n_shadows = gsk_shadow_node_get_n_shadows (node); guint i; /* TODO: Implement blurred shadow nodes */; for (i = 0; i < n_shadows; i ++) { const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i); if (shadow->radius > 0) { render_fallback_node (self, node, builder, vertex_data); return; } } /* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact. * If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */ if (gsk_render_node_get_node_type (shadow_child) == GSK_COLOR_MATRIX_NODE && !color_matrix_modifies_alpha (shadow_child)) { shadow_child = gsk_color_matrix_node_get_child (shadow_child); } for (i = 0; i < n_shadows; i ++) { const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i); const float dx = shadow->dx; const float dy = shadow->dy; int texture_id; gboolean is_offscreen; g_assert (shadow->radius <= 0); if (gsk_render_node_get_node_type (shadow_child) == GSK_TEXT_NODE) { ops_offset (builder, dx, dy); render_text_node (self, shadow_child, builder, &shadow->color, TRUE); ops_offset (builder, - dx, - dy); continue; } min_x = builder->dx + shadow_child->bounds.origin.x; min_y = builder->dy + shadow_child->bounds.origin.y; max_x = min_x + shadow_child->bounds.size.width; max_y = min_y + shadow_child->bounds.size.height; /* Draw the child offscreen, without the offset. */ add_offscreen_ops (self, builder, &shadow_child->bounds, shadow_child, &texture_id, &is_offscreen, RESET_CLIP | RESET_OPACITY); ops_offset (builder, dx, dy); ops_set_program (builder, &self->coloring_program); ops_set_color (builder, &shadow->color); ops_set_texture (builder, texture_id); if (is_offscreen) { const GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = { { { dx + min_x, dy + min_y }, { 0, 1 }, }, { { dx + min_x, dy + max_y }, { 0, 0 }, }, { { dx + max_x, dy + min_y }, { 1, 1 }, }, { { dx + max_x, dy + max_y }, { 1, 0 }, }, { { dx + min_x, dy + max_y }, { 0, 0 }, }, { { dx + max_x, dy + min_y }, { 1, 1 }, }, }; ops_draw (builder, offscreen_vertex_data); } else { const GskQuadVertex onscreen_vertex_data[GL_N_VERTICES] = { { { dx + min_x, dy + min_y }, { 0, 0 }, }, { { dx + min_x, dy + max_y }, { 0, 1 }, }, { { dx + max_x, dy + min_y }, { 1, 0 }, }, { { dx + max_x, dy + max_y }, { 1, 1 }, }, { { dx + min_x, dy + max_y }, { 0, 1 }, }, { { dx + max_x, dy + min_y }, { 1, 0 }, }, }; ops_draw (builder, onscreen_vertex_data); } ops_offset (builder, - dx, - dy); } /* Now draw the child normally */ gsk_gl_renderer_add_render_ops (self, original_child, builder); } static inline void render_cross_fade_node (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node); GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node); float progress = gsk_cross_fade_node_get_progress (node); int start_texture_id; int end_texture_id; gboolean is_offscreen1, is_offscreen2; RenderOp op; const GskQuadVertex vertex_data[GL_N_VERTICES] = { { { min_x, min_y }, { 0, 1 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, { { max_x, max_y }, { 1, 0 }, }, { { min_x, max_y }, { 0, 0 }, }, { { max_x, min_y }, { 1, 1 }, }, }; /* TODO: We create 2 textures here as big as the cross-fade node, but both the * start and the end node might be a lot smaller than that. */ add_offscreen_ops (self, builder, &node->bounds, start_node, &start_texture_id, &is_offscreen1, FORCE_OFFSCREEN | RESET_CLIP); add_offscreen_ops (self, builder, &node->bounds, end_node, &end_texture_id, &is_offscreen2, FORCE_OFFSCREEN | RESET_CLIP); ops_set_program (builder, &self->cross_fade_program); op.op = OP_CHANGE_CROSS_FADE; op.cross_fade.progress = progress; op.cross_fade.source2 = end_texture_id; ops_add (builder, &op); ops_set_texture (builder, start_texture_id); ops_draw (builder, vertex_data); } static inline void apply_viewport_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> New Viewport: %f, %f, %f, %f", op->viewport.origin.x, op->viewport.origin.y, op->viewport.size.width, op->viewport.size.height); glUniform4f (program->viewport_location, op->viewport.origin.x, op->viewport.origin.y, op->viewport.size.width, op->viewport.size.height); glViewport (0, 0, op->viewport.size.width, op->viewport.size.height); } static inline void apply_modelview_op (const Program *program, const RenderOp *op) { float mat[16]; OP_PRINT (" -> Modelview"); graphene_matrix_to_float (&op->modelview, mat); glUniformMatrix4fv (program->modelview_location, 1, GL_FALSE, mat); } static inline void apply_projection_op (const Program *program, const RenderOp *op) { float mat[16]; OP_PRINT (" -> Projection"); graphene_matrix_to_float (&op->projection, mat); glUniformMatrix4fv (program->projection_location, 1, GL_FALSE, mat); } static inline void apply_program_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Program: %d", op->program->index); glUseProgram (op->program->id); } static inline void apply_render_target_op (GskGLRenderer *self, const Program *program, const RenderOp *op) { OP_PRINT (" -> Render Target: %d", op->render_target_id); glBindFramebuffer (GL_FRAMEBUFFER, op->render_target_id); if (op->render_target_id != 0) glDisable (GL_SCISSOR_TEST); else gsk_gl_renderer_setup_render_mode (self); /* Reset glScissor etc. */ } static inline void apply_color_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Color: (%f, %f, %f, %f)", op->color.red, op->color.green, op->color.blue, op->color.alpha); glUniform4f (program->color.color_location, op->color.red, op->color.green, op->color.blue, op->color.alpha); } static inline void apply_opacity_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Opacity %f", op->opacity); glUniform1f (program->alpha_location, op->opacity); } static inline void apply_source_texture_op (const Program *program, const RenderOp *op) { g_assert(op->texture_id != 0); OP_PRINT (" -> New texture: %d", op->texture_id); /* Use texture unit 0 for the source */ glUniform1i (program->source_location, 0); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, op->texture_id); } static inline void apply_color_matrix_op (const Program *program, const RenderOp *op) { float mat[16]; float vec[4]; OP_PRINT (" -> Color Matrix"); graphene_matrix_to_float (&op->color_matrix.matrix, mat); glUniformMatrix4fv (program->color_matrix.color_matrix_location, 1, GL_FALSE, mat); graphene_vec4_to_float (&op->color_matrix.offset, vec); glUniform4fv (program->color_matrix.color_offset_location, 1, vec); } static inline void apply_clip_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Clip (%f, %f, %f, %f) (%f, %f, %f, %f), (%f, %f, %f, %f)", op->clip.bounds.origin.x, op->clip.bounds.origin.y, op->clip.bounds.size.width, op->clip.bounds.size.height, op->clip.corner[0].width, op->clip.corner[1].width, op->clip.corner[2].width, op->clip.corner[3].width, op->clip.corner[0].height, op->clip.corner[1].height, op->clip.corner[2].height, op->clip.corner[3].height); glUniform4f (program->clip_location, op->clip.bounds.origin.x, op->clip.bounds.origin.y, op->clip.bounds.size.width, op->clip.bounds.size.height); glUniform4f (program->clip_corner_widths_location, op->clip.corner[0].width, op->clip.corner[1].width, op->clip.corner[2].width, op->clip.corner[3].width); glUniform4f (program->clip_corner_heights_location, op->clip.corner[0].height, op->clip.corner[1].height, op->clip.corner[2].height, op->clip.corner[3].height); } static inline void apply_inset_shadow_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> inset shadow. Color: (%f, %f, %f, %f), Offset: (%f, %f), Spread: %f, Outline: (%f, %f, %f, %f) Corner widths: (%f, %f, %f, %f), Corner Heights: (%f, %f, %f, %f)", op->inset_shadow.color[0], op->inset_shadow.color[1], op->inset_shadow.color[2], op->inset_shadow.color[3], op->inset_shadow.offset[0], op->inset_shadow.offset[1], op->inset_shadow.spread, op->inset_shadow.outline[0], op->inset_shadow.outline[1], op->inset_shadow.outline[2], op->inset_shadow.outline[3], op->inset_shadow.corner_widths[0], op->inset_shadow.corner_widths[1], op->inset_shadow.corner_widths[2], op->inset_shadow.corner_widths[3], op->inset_shadow.corner_heights[0], op->inset_shadow.corner_heights[1], op->inset_shadow.corner_heights[2], op->inset_shadow.corner_heights[3]); glUniform4fv (program->inset_shadow.color_location, 1, op->inset_shadow.color); glUniform2fv (program->inset_shadow.offset_location, 1, op->inset_shadow.offset); glUniform1f (program->inset_shadow.spread_location, op->inset_shadow.spread); glUniform4fv (program->inset_shadow.outline_location, 1, op->inset_shadow.outline); glUniform4fv (program->inset_shadow.corner_widths_location, 1, op->inset_shadow.corner_widths); glUniform4fv (program->inset_shadow.corner_heights_location, 1, op->inset_shadow.corner_heights); } static inline void apply_unblurred_outset_shadow_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> unblurred outset shadow"); glUniform4fv (program->unblurred_outset_shadow.color_location, 1, op->unblurred_outset_shadow.color); glUniform2fv (program->unblurred_outset_shadow.offset_location, 1, op->unblurred_outset_shadow.offset); glUniform1f (program->unblurred_outset_shadow.spread_location, op->unblurred_outset_shadow.spread); glUniform4fv (program->unblurred_outset_shadow.outline_location, 1, op->unblurred_outset_shadow.outline); glUniform4fv (program->unblurred_outset_shadow.corner_widths_location, 1, op->unblurred_outset_shadow.corner_widths); glUniform4fv (program->unblurred_outset_shadow.corner_heights_location, 1, op->unblurred_outset_shadow.corner_heights); } static inline void apply_outset_shadow_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> outset shadow"); glUniform4fv (program->outset_shadow.outline_location, 1, op->outset_shadow.outline); glUniform4fv (program->outset_shadow.corner_widths_location, 1, op->outset_shadow.corner_widths); glUniform4fv (program->outset_shadow.corner_heights_location, 1, op->outset_shadow.corner_heights); } static inline void apply_linear_gradient_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Linear gradient"); glUniform1i (program->linear_gradient.num_color_stops_location, op->linear_gradient.n_color_stops); glUniform4fv (program->linear_gradient.color_stops_location, op->linear_gradient.n_color_stops, op->linear_gradient.color_stops); glUniform1fv (program->linear_gradient.color_offsets_location, op->linear_gradient.n_color_stops, op->linear_gradient.color_offsets); glUniform2f (program->linear_gradient.start_point_location, op->linear_gradient.start_point.x, op->linear_gradient.start_point.y); glUniform2f (program->linear_gradient.end_point_location, op->linear_gradient.end_point.x, op->linear_gradient.end_point.y); } static inline void apply_border_op (const Program *program, const RenderOp *op) { const GskRoundedRect *o = &op->border.outline; float outline[4]; float widths[4]; float heights[4]; int i; OP_PRINT (" -> Border Outline"); outline[0] = o->bounds.origin.x; outline[1] = o->bounds.origin.y; outline[2] = o->bounds.size.width; outline[3] = o->bounds.size.height; for (i = 0; i < 4; i ++) { widths[i] = o->corner[i].width; heights[i] = o->corner[i].height; } glUniform4fv (program->border.outline_location, 1, outline); glUniform4fv (program->border.corner_widths_location, 1, widths); glUniform4fv (program->border.corner_heights_location, 1, heights); } static inline void apply_border_width_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Border width (%f, %f, %f, %f)", op->border.widths[0], op->border.widths[1], op->border.widths[2], op->border.widths[3]); glUniform4fv (program->border.widths_location, 1, op->border.widths); } static inline void apply_border_color_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Border color (%f, %f, %f, %f)", op->border.color[0], op->border.color[1], op->border.color[2], op->border.color[3]); glUniform4fv (program->border.color_location, 1, op->border.color); } static inline void apply_blur_op (const Program *program, const RenderOp *op) { OP_PRINT (" -> Blur"); glUniform1f (program->blur.blur_radius_location, op->blur.radius); glUniform2f (program->blur.blur_size_location, op->blur.size.width, op->blur.size.height); /*glUniform2f (program->blur.dir_location, op->blur.dir[0], op->blur.dir[1]);*/ } static inline void apply_cross_fade_op (const Program *program, const RenderOp *op) { /* End texture id */ glUniform1i (program->cross_fade.source2_location, 1); glActiveTexture (GL_TEXTURE0 + 1); glBindTexture (GL_TEXTURE_2D, op->cross_fade.source2); /* progress */ glUniform1f (program->cross_fade.progress_location, op->cross_fade.progress); } static void gsk_gl_renderer_dispose (GObject *gobject) { GskGLRenderer *self = GSK_GL_RENDERER (gobject); g_clear_pointer (&self->render_ops, g_array_unref); G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject); } static gboolean gsk_gl_renderer_create_programs (GskGLRenderer *self, GError **error) { GskShaderBuilder *builder; GError *shader_error = NULL; int i; static const struct { const char *name; const char *fs; } program_definitions[] = { { "blit", "blit.fs.glsl" }, { "color", "color.fs.glsl" }, { "coloring", "coloring.fs.glsl" }, { "color matrix", "color_matrix.fs.glsl" }, { "linear gradient", "linear_gradient.fs.glsl" }, { "blur", "blur.fs.glsl" }, { "inset shadow", "inset_shadow.fs.glsl" }, { "outset shadow", "outset_shadow.fs.glsl" }, { "unblurred outset shadow", "unblurred_outset_shadow.fs.glsl" }, { "border", "border.fs.glsl" }, { "cross fade", "cross_fade.fs.glsl" }, }; builder = gsk_shader_builder_new (); gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl"); 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_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS)) gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1"); #endif gsk_shader_builder_set_common_vertex_shader (builder, "blit.vs.glsl", &shader_error); g_assert_no_error (shader_error); for (i = 0; i < GL_N_PROGRAMS; i ++) { Program *prog = &self->programs[i]; prog->index = i; prog->id = gsk_shader_builder_create_program (builder, program_definitions[i].fs, &shader_error); if (shader_error != NULL) { g_propagate_prefixed_error (error, shader_error, "Unable to create '%s' program (from %s and %s):\n", program_definitions[i].name, "blit.vs.glsl", program_definitions[i].fs); g_object_unref (builder); return FALSE; } INIT_COMMON_UNIFORM_LOCATION (prog, alpha); INIT_COMMON_UNIFORM_LOCATION (prog, source); INIT_COMMON_UNIFORM_LOCATION (prog, clip); INIT_COMMON_UNIFORM_LOCATION (prog, clip_corner_widths); INIT_COMMON_UNIFORM_LOCATION (prog, clip_corner_heights); INIT_COMMON_UNIFORM_LOCATION (prog, viewport); INIT_COMMON_UNIFORM_LOCATION (prog, projection); INIT_COMMON_UNIFORM_LOCATION (prog, modelview); } /* color */ INIT_PROGRAM_UNIFORM_LOCATION (color, color); /* coloring */ INIT_PROGRAM_UNIFORM_LOCATION (coloring, color); /* color matrix */ INIT_PROGRAM_UNIFORM_LOCATION (color_matrix, color_matrix); INIT_PROGRAM_UNIFORM_LOCATION (color_matrix, color_offset); /* linear gradient */ INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, color_stops); INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, color_offsets); INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, num_color_stops); INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, start_point); INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, end_point); /* blur */ INIT_PROGRAM_UNIFORM_LOCATION (blur, blur_radius); INIT_PROGRAM_UNIFORM_LOCATION (blur, blur_size); /*INIT_PROGRAM_UNIFORM_LOCATION (blur, dir);*/ /* inset shadow */ INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, color); INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, spread); INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, offset); INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, outline); INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, corner_widths); INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, corner_heights); /* outset shadow */ INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, outline); INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, corner_widths); INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, corner_heights); /* unblurred outset shadow */ INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, color); INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, spread); INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, offset); INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, outline); INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, corner_widths); INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, corner_heights); /* border */ INIT_PROGRAM_UNIFORM_LOCATION (border, color); INIT_PROGRAM_UNIFORM_LOCATION (border, widths); INIT_PROGRAM_UNIFORM_LOCATION (border, outline); INIT_PROGRAM_UNIFORM_LOCATION (border, corner_widths); INIT_PROGRAM_UNIFORM_LOCATION (border, corner_heights); /* cross fade */ INIT_PROGRAM_UNIFORM_LOCATION (cross_fade, progress); INIT_PROGRAM_UNIFORM_LOCATION (cross_fade, source2); g_object_unref (builder); return TRUE; } static gboolean gsk_gl_renderer_realize (GskRenderer *renderer, GdkSurface *surface, 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_surface_create_gl_context (surface, 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_profiler = gsk_gl_profiler_new (self->gl_context); self->gl_driver = gsk_gl_driver_new (self->gl_context); GSK_RENDERER_NOTE (renderer, OPENGL, g_message ("Creating buffers and programs")); if (!gsk_gl_renderer_create_programs (self, error)) return FALSE; gsk_gl_glyph_cache_init (&self->glyph_cache, renderer, self->gl_driver); gsk_gl_shadow_cache_init (&self->shadow_cache); return TRUE; } static void gsk_gl_renderer_unrealize (GskRenderer *renderer) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); guint i; 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_array_set_size (self->render_ops, 0); for (i = 0; i < GL_N_PROGRAMS; i ++) glDeleteProgram (self->programs[i].id); gsk_gl_glyph_cache_free (&self->glyph_cache); gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver); 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 (); g_clear_object (&self->gl_context); } static void gsk_gl_renderer_clear_tree (GskGLRenderer *self) { int removed_textures; if (self->gl_context == NULL) return; gdk_gl_context_make_current (self->gl_context); g_array_remove_range (self->render_ops, 0, self->render_ops->len); removed_textures = gsk_gl_driver_collect_textures (self->gl_driver); GSK_RENDERER_NOTE (GSK_RENDERER (self), OPENGL, g_message ("Collected: %d textures", removed_textures)); } static void gsk_gl_renderer_clear (GskGLRenderer *self) { GSK_RENDERER_NOTE (GSK_RENDERER (self), OPENGL, g_message ("Clearing viewport")); 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) { if (self->render_region == NULL) { glDisable (GL_SCISSOR_TEST); } else { GdkSurface *surface = gsk_renderer_get_surface (GSK_RENDERER (self)); cairo_rectangle_int_t extents; int surface_height; g_assert (cairo_region_num_rectangles (self->render_region) == 1); surface_height = gdk_surface_get_height (surface) * self->scale_factor; cairo_region_get_rectangle (self->render_region, 0, &extents); glEnable (GL_SCISSOR_TEST); glScissor (extents.x * self->scale_factor, surface_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor), extents.width * self->scale_factor, extents.height * self->scale_factor); } } static void gsk_gl_renderer_add_render_ops (GskGLRenderer *self, GskRenderNode *node, RenderOpBuilder *builder) { const float min_x = builder->dx + node->bounds.origin.x; const float min_y = builder->dy + node->bounds.origin.y; const float max_x = min_x + node->bounds.size.width; const float max_y = min_y + node->bounds.size.height; /* Default vertex data */ const GskQuadVertex vertex_data[GL_N_VERTICES] = { { { min_x, min_y }, { 0, 0 }, }, { { min_x, max_y }, { 0, 1 }, }, { { max_x, min_y }, { 1, 0 }, }, { { max_x, max_y }, { 1, 1 }, }, { { min_x, max_y }, { 0, 1 }, }, { { max_x, min_y }, { 1, 0 }, }, }; /* This can still happen, even if the render nodes are created using * GtkSnapshot, so let's juse be safe. */ if (node->bounds.size.width == 0.0f || node->bounds.size.height == 0.0f) return; /* Check whether the render node is entirely out of the current * already transformed clip region */ { graphene_rect_t transformed_node_bounds; ops_transform_bounds_modelview (builder, &node->bounds, &transformed_node_bounds); if (!graphene_rect_intersection (&builder->current_clip->bounds, &transformed_node_bounds, NULL)) return; } switch (gsk_render_node_get_node_type (node)) { case GSK_NOT_A_RENDER_NODE: g_assert_not_reached (); 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_ops (self, child, builder); } } break; case GSK_DEBUG_NODE: gsk_gl_renderer_add_render_ops (self, gsk_debug_node_get_child (node), builder); break; case GSK_COLOR_NODE: render_color_node (self, node, builder, vertex_data); break; case GSK_TEXTURE_NODE: render_texture_node (self, node, builder); break; case GSK_OFFSET_NODE: render_offset_node (self, node, builder); break; case GSK_TRANSFORM_NODE: render_transform_node (self, node, builder); break; case GSK_OPACITY_NODE: render_opacity_node (self, node, builder); break; case GSK_LINEAR_GRADIENT_NODE: render_linear_gradient_node (self, node, builder, vertex_data); break; case GSK_CLIP_NODE: render_clip_node (self, node, builder); break; case GSK_ROUNDED_CLIP_NODE: render_rounded_clip_node (self, node, builder); break; case GSK_TEXT_NODE: render_text_node (self, node, builder, gsk_text_node_peek_color (node), FALSE); break; case GSK_COLOR_MATRIX_NODE: render_color_matrix_node (self, node, builder, vertex_data); break; case GSK_BLUR_NODE: render_blur_node (self, node, builder, vertex_data); break; case GSK_INSET_SHADOW_NODE: render_inset_shadow_node (self, node, builder, vertex_data); break; case GSK_OUTSET_SHADOW_NODE: if (gsk_outset_shadow_node_get_blur_radius (node) > 0) render_outset_shadow_node (self, node, builder); else render_unblurred_outset_shadow_node (self, node, builder, vertex_data); break; case GSK_SHADOW_NODE: render_shadow_node (self, node, builder, vertex_data); break; case GSK_BORDER_NODE: render_border_node (self, node, builder); break; case GSK_CROSS_FADE_NODE: render_cross_fade_node (self, node, builder); break; case GSK_REPEATING_LINEAR_GRADIENT_NODE: case GSK_BLEND_NODE: case GSK_REPEAT_NODE: case GSK_CAIRO_NODE: default: { render_fallback_node (self, node, builder, vertex_data); } } } static void add_offscreen_ops (GskGLRenderer *self, RenderOpBuilder *builder, const graphene_rect_t *bounds, GskRenderNode *child_node, int *texture_id_out, gboolean *is_offscreen, guint flags) { const float scale = ops_get_scale (builder); const float width = bounds->size.width * scale; const float height = bounds->size.height * scale; const float dx = builder->dx; const float dy = builder->dy; int render_target; int prev_render_target; RenderOp op; graphene_matrix_t identity; graphene_matrix_t prev_projection; graphene_rect_t prev_viewport; graphene_matrix_t item_proj; float prev_opacity; int texture_id = 0; /* We need the child node as a texture. If it already is one, we don't need to draw * it on a framebuffer of course. */ if (gsk_render_node_get_node_type (child_node) == GSK_TEXTURE_NODE && (flags & FORCE_OFFSCREEN) == 0) { GdkTexture *texture = gsk_texture_node_get_texture (child_node); int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST; get_gl_scaling_filters (child_node, &gl_min_filter, &gl_mag_filter); *texture_id_out = gsk_gl_driver_get_texture_for_texture (self->gl_driver, texture, gl_min_filter, gl_mag_filter); *is_offscreen = FALSE; return; } /* Check if we've already cached the drawn texture. */ { const int cached_id = gsk_gl_driver_get_texture_for_pointer (self->gl_driver, child_node); if (cached_id != 0) { *texture_id_out = cached_id; /* We didn't render it offscreen, but hand out an offscreen texture id */ *is_offscreen = TRUE; return; } } texture_id = gsk_gl_driver_create_texture (self->gl_driver, width, height); gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id); gsk_gl_driver_init_texture_empty (self->gl_driver, texture_id); render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture_id, TRUE, TRUE); graphene_matrix_init_ortho (&item_proj, bounds->origin.x * scale, (bounds->origin.x + bounds->size.width) * scale, bounds->origin.y * scale, (bounds->origin.y + bounds->size.height) * scale, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); graphene_matrix_scale (&item_proj, 1, -1, 1); graphene_matrix_init_identity (&identity); graphene_matrix_scale (&identity, scale, scale, 1); prev_render_target = ops_set_render_target (builder, render_target); /* Clear since we use this rendertarget for the first time */ op.op = OP_CLEAR; ops_add (builder, &op); prev_projection = ops_set_projection (builder, &item_proj); ops_push_modelview (builder, &identity); prev_viewport = ops_set_viewport (builder, &GRAPHENE_RECT_INIT (bounds->origin.x * scale, bounds->origin.y * scale, width, height)); if (flags & RESET_CLIP) ops_push_clip (builder, &GSK_ROUNDED_RECT_INIT (bounds->origin.x * scale, bounds->origin.y * scale, width, height)); builder->dx = 0; builder->dy = 0; if (flags & RESET_OPACITY) prev_opacity = ops_set_opacity (builder, 1.0); gsk_gl_renderer_add_render_ops (self, child_node, builder); if (flags & RESET_OPACITY) ops_set_opacity (builder, prev_opacity); builder->dx = dx; builder->dy = dy; if (flags & RESET_CLIP) ops_pop_clip (builder); ops_set_viewport (builder, &prev_viewport); ops_pop_modelview (builder); ops_set_projection (builder, &prev_projection); ops_set_render_target (builder, prev_render_target); *is_offscreen = TRUE; *texture_id_out = texture_id; gsk_gl_driver_set_texture_for_pointer (self->gl_driver, child_node, texture_id); } static void gsk_gl_renderer_render_ops (GskGLRenderer *self, gsize vertex_data_size) { guint i; guint n_ops = self->render_ops->len; const Program *program = NULL; gsize buffer_index = 0; float *vertex_data = g_malloc (vertex_data_size); /*g_message ("%s: Buffer size: %ld", __FUNCTION__, vertex_data_size);*/ GLuint buffer_id, vao_id; glGenVertexArrays (1, &vao_id); glBindVertexArray (vao_id); glGenBuffers (1, &buffer_id); glBindBuffer (GL_ARRAY_BUFFER, buffer_id); // Fill buffer data for (i = 0; i < n_ops; i ++) { const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i); if (op->op == OP_CHANGE_VAO) { memcpy (vertex_data + buffer_index, &op->vertex_data, sizeof (GskQuadVertex) * GL_N_VERTICES); buffer_index += sizeof (GskQuadVertex) * GL_N_VERTICES / sizeof (float); } } // Set buffer data glBufferData (GL_ARRAY_BUFFER, vertex_data_size, vertex_data, GL_STATIC_DRAW); // Describe buffer contents /* 0 = position location */ glEnableVertexAttribArray (0); glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, sizeof (GskQuadVertex), (void *) G_STRUCT_OFFSET (GskQuadVertex, position)); /* 1 = texture coord location */ glEnableVertexAttribArray (1); glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, sizeof (GskQuadVertex), (void *) G_STRUCT_OFFSET (GskQuadVertex, uv)); for (i = 0; i < n_ops; i ++) { const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i); if (op->op == OP_NONE || op->op == OP_CHANGE_VAO) continue; if (op->op != OP_CHANGE_PROGRAM && op->op != OP_CHANGE_RENDER_TARGET && op->op != OP_CLEAR && program == NULL) continue; OP_PRINT ("Op %u: %u", i, op->op); switch (op->op) { case OP_CHANGE_PROJECTION: apply_projection_op (program, op); break; case OP_CHANGE_MODELVIEW: apply_modelview_op (program, op); break; case OP_CHANGE_PROGRAM: apply_program_op (program, op); program = op->program; break; case OP_CHANGE_RENDER_TARGET: apply_render_target_op (self, program, op); break; case OP_CLEAR: glClearColor (0, 0, 0, 0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); break; case OP_CHANGE_VIEWPORT: apply_viewport_op (program, op); break; case OP_CHANGE_OPACITY: apply_opacity_op (program, op); break; case OP_CHANGE_COLOR_MATRIX: apply_color_matrix_op (program, op); break; case OP_CHANGE_COLOR: /*g_assert (program == &self->color_program || program == &self->coloring_program ||*/ /*program == &self->shadow_program);*/ apply_color_op (program, op); break; case OP_CHANGE_BORDER_COLOR: apply_border_color_op (program, op); break; case OP_CHANGE_CLIP: apply_clip_op (program, op); break; case OP_CHANGE_SOURCE_TEXTURE: apply_source_texture_op (program, op); break; case OP_CHANGE_CROSS_FADE: g_assert (program == &self->cross_fade_program); apply_cross_fade_op (program, op); break; case OP_CHANGE_LINEAR_GRADIENT: apply_linear_gradient_op (program, op); break; case OP_CHANGE_BLUR: apply_blur_op (program, op); break; case OP_CHANGE_INSET_SHADOW: apply_inset_shadow_op (program, op); break; case OP_CHANGE_OUTSET_SHADOW: apply_outset_shadow_op (program, op); break; case OP_CHANGE_BORDER: apply_border_op (program, op); break; case OP_CHANGE_BORDER_WIDTH: apply_border_width_op (program, op); break; case OP_CHANGE_UNBLURRED_OUTSET_SHADOW: apply_unblurred_outset_shadow_op (program, op); break; case OP_DRAW: OP_PRINT (" -> draw %ld, size %ld and program %d\n", op->draw.vao_offset, op->draw.vao_size, program->index); glDrawArrays (GL_TRIANGLES, op->draw.vao_offset, op->draw.vao_size); break; case OP_DUMP_FRAMEBUFFER: dump_framebuffer (op->dump.filename, op->dump.width, op->dump.height); break; default: g_warn_if_reached (); } OP_PRINT ("\n"); } /* Done drawing, destroy the buffer again. * TODO: Can we reuse the memory, though? */ g_free (vertex_data); glDeleteVertexArrays (1, &vao_id); glDeleteBuffers (1, &buffer_id); } static void gsk_gl_renderer_do_render (GskRenderer *renderer, GskRenderNode *root, const graphene_rect_t *viewport, int fbo_id, int scale_factor) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); RenderOpBuilder render_op_builder; graphene_matrix_t modelview, projection; #ifdef G_ENABLE_DEBUG GskProfiler *profiler; gint64 gpu_time, cpu_time; #endif #ifdef G_ENABLE_DEBUG profiler = gsk_renderer_get_profiler (renderer); #endif if (self->gl_context == NULL) { GSK_RENDERER_NOTE (renderer, OPENGL, g_message ("No valid GL context associated to the renderer")); return; } /* 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, viewport->origin.y, viewport->origin.y + viewport->size.height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); graphene_matrix_scale (&projection, 1, -1, 1); gsk_gl_driver_begin_frame (self->gl_driver); gsk_gl_glyph_cache_begin_frame (&self->glyph_cache); gsk_gl_shadow_cache_begin_frame (&self->shadow_cache, self->gl_driver); memset (&render_op_builder, 0, sizeof (render_op_builder)); render_op_builder.renderer = self; render_op_builder.current_projection = projection; render_op_builder.current_viewport = *viewport; render_op_builder.current_opacity = 1.0f; render_op_builder.render_ops = self->render_ops; ops_push_modelview (&render_op_builder, &modelview); /* Initial clip is self->render_region! */ if (self->render_region != NULL) { GskRoundedRect transformed_render_region = { 0, }; cairo_rectangle_int_t render_extents; cairo_region_get_extents (self->render_region, &render_extents); ops_transform_bounds_modelview (&render_op_builder, &GRAPHENE_RECT_INIT (render_extents.x, render_extents.y, render_extents.width, render_extents.height), &transformed_render_region.bounds); ops_push_clip (&render_op_builder, &transformed_render_region); } else { ops_push_clip (&render_op_builder, &GSK_ROUNDED_RECT_INIT (viewport->origin.x, viewport->origin.y, viewport->size.width, viewport->size.height)); } if (fbo_id != 0) ops_set_render_target (&render_op_builder, fbo_id); gsk_gl_renderer_add_render_ops (self, root, &render_op_builder); /* We correctly reset the state everywhere */ g_assert_cmpint (render_op_builder.current_render_target, ==, fbo_id); ops_pop_modelview (&render_op_builder); ops_pop_clip (&render_op_builder); ops_finish (&render_op_builder); /*g_message ("Ops: %u", self->render_ops->len);*/ /* Now actually draw things... */ #ifdef G_ENABLE_DEBUG gsk_gl_profiler_begin_gpu_region (self->gl_profiler); gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time); #endif glViewport (0, 0, ceilf (viewport->size.width), ceilf (viewport->size.height)); gsk_gl_renderer_setup_render_mode (self); gsk_gl_renderer_clear (self); glEnable (GL_DEPTH_TEST); glDepthFunc (GL_LEQUAL); /* Pre-multiplied alpha! */ glEnable (GL_BLEND); glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation (GL_FUNC_ADD); gsk_gl_renderer_render_ops (self, render_op_builder.buffer_size); 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 GdkTexture * gsk_gl_renderer_render_texture (GskRenderer *renderer, GskRenderNode *root, const graphene_rect_t *viewport) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); GdkTexture *texture; int width, height; guint texture_id; guint fbo_id; g_return_val_if_fail (self->gl_context != NULL, NULL); width = ceilf (viewport->size.width); height = ceilf (viewport->size.height); self->scale_factor = gdk_surface_get_scale_factor (gsk_renderer_get_surface (renderer)); gdk_gl_context_make_current (self->gl_context); /* Prepare our framebuffer */ gsk_gl_driver_begin_frame (self->gl_driver); glGenTextures (1, &texture_id); glBindTexture (GL_TEXTURE_2D, texture_id); if (gdk_gl_context_get_use_es (self->gl_context)) glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); else glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glGenFramebuffers (1, &fbo_id); glBindFramebuffer (GL_FRAMEBUFFER, fbo_id); glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0); g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE); gsk_gl_renderer_clear (self); gsk_gl_driver_end_frame (self->gl_driver); /* Render the actual scene */ gsk_gl_renderer_do_render (renderer, root, viewport, fbo_id, 1); texture = gdk_gl_texture_new (self->gl_context, texture_id, width, height, NULL, NULL); gsk_gl_renderer_clear_tree (self); return texture; } static void gsk_gl_renderer_render (GskRenderer *renderer, GskRenderNode *root, const cairo_region_t *update_area) { GskGLRenderer *self = GSK_GL_RENDERER (renderer); graphene_rect_t viewport; const cairo_region_t *damage; GdkRectangle whole_surface; GdkSurface *surface; if (self->gl_context == NULL) return; surface = gsk_renderer_get_surface (renderer); whole_surface = (GdkRectangle) { 0, 0, gdk_surface_get_width (surface) * self->scale_factor, gdk_surface_get_height (surface) * self->scale_factor }; gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->gl_context), update_area); damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (self->gl_context)); if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN) { self->render_region = NULL; } else { GdkRectangle extents; cairo_region_get_extents (damage, &extents); if (gdk_rectangle_equal (&extents, &whole_surface)) self->render_region = NULL; else self->render_region = cairo_region_create_rectangle (&extents); } self->scale_factor = gdk_surface_get_scale_factor (surface); gdk_gl_context_make_current (self->gl_context); viewport.origin.x = 0; viewport.origin.y = 0; viewport.size.width = gdk_surface_get_width (surface) * self->scale_factor; viewport.size.height = gdk_surface_get_height (surface) * self->scale_factor; gsk_gl_renderer_do_render (renderer, root, &viewport, 0, self->scale_factor); gdk_gl_context_make_current (self->gl_context); gsk_gl_renderer_clear_tree (self); gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->gl_context)); g_clear_pointer (&self->render_region, cairo_region_destroy); } 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->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 (); self->render_ops = g_array_new (FALSE, FALSE, sizeof (RenderOp)); #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 }