From 7bea2d3255cc7f81e2c78e1acfe2ef17948fc1be Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 13 Mar 2021 23:30:18 -0500 Subject: ngl: Special-case css backgrounds Recognize a common pattern: A rounded clip with a color node, followed by a border node, with the same outline. This is what CSS backgrounds frequently produce, and we can render it more efficiently with a combined shader. --- gsk/meson.build | 1 + gsk/ngl/gsknglcommandqueue.c | 6 ++++ gsk/ngl/gskngldriver.c | 2 ++ gsk/ngl/gsknglprograms.defs | 5 +++ gsk/ngl/gsknglrenderjob.c | 69 ++++++++++++++++++++++++++++++++++++ gsk/ngl/gskngltypesprivate.h | 1 + gsk/ngl/resources/filled_border.glsl | 47 ++++++++++++++++++++++++ gsk/ngl/resources/preamble.vs.glsl | 2 ++ 8 files changed, 133 insertions(+) create mode 100644 gsk/ngl/resources/filled_border.glsl diff --git a/gsk/meson.build b/gsk/meson.build index 8f9a6281c0..8e2b0f1782 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -40,6 +40,7 @@ gsk_private_ngl_shaders = [ 'ngl/resources/blend.glsl', 'ngl/resources/repeat.glsl', 'ngl/resources/custom.glsl', + 'ngl/resources/filled_border.glsl', ] gsk_public_sources = files([ diff --git a/gsk/ngl/gsknglcommandqueue.c b/gsk/ngl/gsknglcommandqueue.c index aff201cb4a..bebd315823 100644 --- a/gsk/ngl/gsknglcommandqueue.c +++ b/gsk/ngl/gsknglcommandqueue.c @@ -1010,6 +1010,12 @@ gsk_ngl_command_queue_execute (GskNglCommandQueue *self, sizeof (GskNglDrawVertex), (void *) G_STRUCT_OFFSET (GskNglDrawVertex, color)); + /* 3 = color2 location */ + glEnableVertexAttribArray (3); + glVertexAttribPointer (3, 4, GL_FLOAT, GL_FALSE, + sizeof (GskNglDrawVertex), + (void *) G_STRUCT_OFFSET (GskNglDrawVertex, color2)); + /* Setup initial scissor clip */ if (scissor != NULL) { diff --git a/gsk/ngl/gskngldriver.c b/gsk/ngl/gskngldriver.c index 3d910e5d51..93763dbb12 100644 --- a/gsk/ngl/gskngldriver.c +++ b/gsk/ngl/gskngldriver.c @@ -348,6 +348,7 @@ gsk_ngl_driver_load_programs (GskNglDriver *self, gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0); gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1); gsk_ngl_compiler_bind_attribute (compiler, "aColor", 2); + gsk_ngl_compiler_bind_attribute (compiler, "aColor2", 3); /* Use XMacros to register all of our programs and their uniforms */ #define GSK_NGL_NO_UNIFORMS @@ -1046,6 +1047,7 @@ gsk_ngl_driver_lookup_shader (GskNglDriver *self, gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0); gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1); gsk_ngl_compiler_bind_attribute (compiler, "aColor", 2); + gsk_ngl_compiler_bind_attribute (compiler, "aColor2", 3); if ((program = gsk_ngl_compiler_compile (compiler, NULL, "", error))) { diff --git a/gsk/ngl/gsknglprograms.defs b/gsk/ngl/gsknglprograms.defs index 8af9140eed..f4f4a0dfe5 100644 --- a/gsk/ngl/gsknglprograms.defs +++ b/gsk/ngl/gsknglprograms.defs @@ -42,6 +42,11 @@ GSK_NGL_DEFINE_PROGRAM (cross_fade, GSK_NGL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress) GSK_NGL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2)) +GSK_NGL_DEFINE_PROGRAM (filled_border, + "/org/gtk/libgsk/ngl/filled_border.glsl", + GSK_NGL_ADD_UNIFORM (1, FILLED_BORDER_WIDTHS, u_widths) + GSK_NGL_ADD_UNIFORM (2, FILLED_BORDER_OUTLINE_RECT, u_outline_rect)) + GSK_NGL_DEFINE_PROGRAM (inset_shadow, "/org/gtk/libgsk/ngl/inset_shadow.glsl", GSK_NGL_ADD_UNIFORM (1, INSET_SHADOW_SPREAD, u_spread) diff --git a/gsk/ngl/gsknglrenderjob.c b/gsk/ngl/gsknglrenderjob.c index 335ca960c8..dc7935b699 100644 --- a/gsk/ngl/gsknglrenderjob.c +++ b/gsk/ngl/gsknglrenderjob.c @@ -1794,6 +1794,56 @@ gsk_ngl_render_job_visit_border_node (GskNglRenderJob *job, gsk_ngl_render_job_end_draw (job); } +/* A special case for a pattern that occurs frequently with CSS + * backgrounds: two sibling nodes, the first of which is a rounded + * clip node with a color node as child, and the second one is a + * border node, with the same outline as the clip node. We render + * this using the filled_border shader. + */ +static void +gsk_ngl_render_job_visit_css_background (GskNglRenderJob *job, + const GskRenderNode *node, + const GskRenderNode *node2) +{ + const GskRenderNode *child = gsk_rounded_clip_node_get_child (node); + const GdkRGBA *c2 = gsk_color_node_get_color (child); + const GdkRGBA *c = gsk_border_node_get_colors (node2); + const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node2); + const float *widths = gsk_border_node_get_widths (node2); + float min_x = job->offset_x + node2->bounds.origin.x; + float min_y = job->offset_y + node2->bounds.origin.y; + float max_x = min_x + node2->bounds.size.width; + float max_y = min_y + node2->bounds.size.height; + GskRoundedRect outline; + GskNglDrawVertex *vertices; + + if (node_is_invisible (node2)) + return; + + gsk_ngl_render_job_transform_rounded_rect (job, rounded_outline, &outline); + + gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border)); + + gsk_ngl_program_set_uniform4fv (job->current_program, + UNIFORM_FILLED_BORDER_WIDTHS, 0, + 1, + widths); + gsk_ngl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_FILLED_BORDER_OUTLINE_RECT, 0, + &outline); + + vertices = gsk_ngl_command_queue_add_vertices (job->command_queue); + + vertices[0] = (GskNglDrawVertex) { { min_x, min_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + vertices[1] = (GskNglDrawVertex) { { min_x, max_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + vertices[2] = (GskNglDrawVertex) { { max_x, min_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + vertices[3] = (GskNglDrawVertex) { { max_x, max_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + vertices[4] = (GskNglDrawVertex) { { min_x, max_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + vertices[5] = (GskNglDrawVertex) { { max_x, min_y }, { 0, 0 }, { c[0].red, c[0].green, c[0].blue, c[0].alpha }, { c2->red, c2->green, c2->blue, c2->alpha } }; + + gsk_ngl_render_job_end_draw (job); +} + /* Returns TRUE if applying @transform to @bounds * yields an axis-aligned rectangle */ @@ -3334,6 +3384,25 @@ gsk_ngl_render_job_visit_node (GskNglRenderJob *job, for (guint i = 0; i < n_children; i++) { const GskRenderNode *child = gsk_container_node_get_child (node, i); + + if (i + 1 < n_children && + job->current_clip->is_fully_contained && + gsk_render_node_get_node_type (child) == GSK_ROUNDED_CLIP_NODE) + { + const GskRenderNode *grandchild = gsk_rounded_clip_node_get_child (child); + const GskRenderNode *child2 = gsk_container_node_get_child (node, i + 1); + if (gsk_render_node_get_node_type (grandchild) == GSK_COLOR_NODE && + gsk_render_node_get_node_type (child2) == GSK_BORDER_NODE && + gsk_border_node_get_uniform_color (child2) && + gsk_rounded_rect_equal (gsk_rounded_clip_node_get_clip (child), + gsk_border_node_get_outline (child2))) + { + gsk_ngl_render_job_visit_css_background (job, child, child2); + i++; /* skip the border node */ + continue; + } + } + gsk_ngl_render_job_visit_node (job, child); } } diff --git a/gsk/ngl/gskngltypesprivate.h b/gsk/ngl/gskngltypesprivate.h index dc76fb41f4..a65130c19f 100644 --- a/gsk/ngl/gskngltypesprivate.h +++ b/gsk/ngl/gskngltypesprivate.h @@ -56,6 +56,7 @@ struct _GskNglDrawVertex float position[2]; float uv[2]; float color[4]; + float color2[4]; }; G_END_DECLS diff --git a/gsk/ngl/resources/filled_border.glsl b/gsk/ngl/resources/filled_border.glsl new file mode 100644 index 0000000000..d503dd0500 --- /dev/null +++ b/gsk/ngl/resources/filled_border.glsl @@ -0,0 +1,47 @@ +// VERTEX_SHADER: +// filled_border.glsl + +uniform vec4 u_widths; +uniform vec4[3] u_outline_rect; + +_OUT_ vec4 outer_color; +_OUT_ vec4 inner_color; +_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline; +_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline; + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + outer_color = gsk_scaled_premultiply(aColor, u_alpha); + inner_color = gsk_scaled_premultiply(aColor2, u_alpha); + + GskRoundedRect outside = gsk_create_rect(u_outline_rect); + GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths); + + gsk_rounded_rect_transform(outside, u_modelview); + gsk_rounded_rect_transform(inside, u_modelview); + + gsk_rounded_rect_encode(outside, transformed_outside_outline); + gsk_rounded_rect_encode(inside, transformed_inside_outline); +} + +// FRAGMENT_SHADER: +// filled_border.glsl + +uniform vec4[3] u_outline_rect; + +_IN_ vec4 outer_color; +_IN_ vec4 inner_color; +_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline; +_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline; + +void main() { + vec2 frag = gsk_get_frag_coord(); + float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag); + float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag); + + float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0); + float alpha2 = clamp(inner_coverage, 0.0, 1.0); + + gskSetOutputColor((outer_color * alpha) + (inner_color * alpha2)); +} diff --git a/gsk/ngl/resources/preamble.vs.glsl b/gsk/ngl/resources/preamble.vs.glsl index a549ff6ded..758bca89d2 100644 --- a/gsk/ngl/resources/preamble.vs.glsl +++ b/gsk/ngl/resources/preamble.vs.glsl @@ -6,11 +6,13 @@ uniform float u_alpha; attribute vec2 aPosition; attribute vec2 aUv; attribute vec4 aColor; +attribute vec4 aColor2; _OUT_ vec2 vUv; #else _IN_ vec2 aPosition; _IN_ vec2 aUv; _IN_ vec4 aColor; +_IN_ vec4 aColor2; _OUT_ vec2 vUv; #endif -- cgit v1.2.1