diff --git a/gsk/gl/gskglprograms.defs b/gsk/gl/gskglprograms.defs index 9afb826bf6..42d2a6d5fe 100644 --- a/gsk/gl/gskglprograms.defs +++ b/gsk/gl/gskglprograms.defs @@ -87,3 +87,22 @@ GSK_GL_DEFINE_PROGRAM (unblurred_outset_shadow, GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread) GSK_GL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset) GSK_GL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect)) + +GSK_GL_DEFINE_PROGRAM (glyphy, + GSK_GL_SHADER_JOINED (VERTEX, + GSK_GL_SHADER_RESOURCE ("glyphy.vs.glsl"), + NULL) + GSK_GL_SHADER_JOINED (FRAGMENT, + GSK_GL_SHADER_RESOURCE ("glyphy.atlas.glsl"), + GSK_GL_SHADER_STRING (glyphy_common_shader_source ()), + GSK_GL_SHADER_STRING ("#define GLYPHY_SDF_PSEUDO_DISTANCE 1\n"), + GSK_GL_SHADER_STRING (glyphy_sdf_shader_source ()), + GSK_GL_SHADER_RESOURCE ("glyphy.fs.glsl"), + NULL), + GSK_GL_ADD_UNIFORM (0, GLYPHY_CONTRAST, u_contrast) + GSK_GL_ADD_UNIFORM (1, GLYPHY_GAMMA_ADJUST, u_gamma_adjust) + GSK_GL_ADD_UNIFORM (2, GLYPHY_OUTLINE_THICKNESS, u_outline_thickness) + GSK_GL_ADD_UNIFORM (3, GLYPHY_OUTLINE, u_outline) + GSK_GL_ADD_UNIFORM (4, GLYPHY_BOLDNESS, u_boldness) + GSK_GL_ADD_UNIFORM (5, GLYPHY_DEBUG, u_debug) + GSK_GL_ADD_UNIFORM (6, GLYPHY_ATLAS_INFO, u_atlas_info)) diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index 44e72d4944..5cbd3040d1 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -39,6 +39,7 @@ #include "gskglcommandqueueprivate.h" #include "gskgldriverprivate.h" #include "gskglglyphlibraryprivate.h" +#include "gskglglyphylibraryprivate.h" #include "gskgliconlibraryprivate.h" #include "gskglprogramprivate.h" #include "gskglrenderjobprivate.h" @@ -3093,12 +3094,193 @@ gsk_gl_render_job_visit_text_node_legacy (GskGLRenderJob *job, } } +/* Keep this in sync with glyph_vertex_transcode in glyphy.vs.glsl */ +typedef struct +{ + float x; + float y; + float g16hi; + float g16lo; +} EncodedGlyph; + +static unsigned int +glyph_encode (guint atlas_x , /* 7 bits */ + guint atlas_y, /* 7 bits */ + guint corner_x, /* 1 bit */ + guint corner_y, /* 1 bit */ + guint nominal_w, /* 6 bits */ + guint nominal_h) /* 6 bits */ +{ + guint x, y; + + g_assert (0 == (atlas_x & ~0x7F)); + g_assert (0 == (atlas_y & ~0x7F)); + g_assert (0 == (corner_x & ~1)); + g_assert (0 == (corner_y & ~1)); + g_assert (0 == (nominal_w & ~0x3F)); + g_assert (0 == (nominal_h & ~0x3F)); + + x = (((atlas_x << 6) | nominal_w) << 1) | corner_x; + y = (((atlas_y << 6) | nominal_h) << 1) | corner_y; + + return (x << 16) | y; +} + +static void +encoded_glyph_init (EncodedGlyph *eg, + float x, + float y, + guint corner_x, + guint corner_y, + const GskGLGlyphyValue *gi) +{ + guint encoded = glyph_encode (gi->atlas_x, gi->atlas_y, corner_x, corner_y, gi->nominal_w, gi->nominal_h); + + eg->x = x; + eg->y = y; + eg->g16hi = encoded >> 16; + eg->g16lo = encoded & 0xFFFF; +} + +static inline void +add_encoded_glyph (GskGLDrawVertex *vertices, + const EncodedGlyph *eg, + const guint16 c[4]) +{ + *vertices = (GskGLDrawVertex) { .position = { eg->x, eg->y}, .uv = { eg->g16hi, eg->g16lo}, .color = { c[0], c[1], c[2], c[3] } }; +} + static inline void gsk_gl_render_job_visit_text_node_glyphy (GskGLRenderJob *job, const GskRenderNode *node, - const GdkRGBA *color, - gboolean force_color) + const GdkRGBA *color) { + const graphene_point_t *offset; + const PangoGlyphInfo *glyphs; + const PangoGlyphInfo *gi; + GskGLGlyphyLibrary *library; + GskGLCommandBatch *batch; + const PangoFont *font; + GskGLDrawVertex *vertices; + const guint16 *c; + GskGLGlyphyKey lookup; + guint16 cc[4]; + float x; + float y; + guint last_texture = 0; + guint num_glyphs; + guint used = 0; + guint i; + int x_position = 0; + + g_assert (!gsk_text_node_has_color_glyphs (node)); + + if (!(num_glyphs = gsk_text_node_get_num_glyphs (node))) + return; + + if (RGBA_IS_CLEAR (color)) + return; + + font = gsk_text_node_get_font (node); + glyphs = gsk_text_node_get_glyphs (node, NULL); + library = job->driver->glyphy_library; + offset = gsk_text_node_get_offset (node); + x = offset->x + job->offset_x; + y = offset->y + job->offset_y; + + rgba_to_half (color, cc); + c = cc; + + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, glyphy)); + + batch = gsk_gl_command_queue_get_batch (job->command_queue); + vertices = gsk_gl_command_queue_add_n_vertices (job->command_queue, num_glyphs); + + lookup.font = (PangoFont *)font; + + for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++) + { + const GskGLGlyphyValue *glyph; + float cx = 0, cy = 0; + guint texture_id; + + lookup.glyph = gi->glyph; + texture_id = gsk_gl_glyphy_library_lookup_or_add (library, &lookup, &glyph); + if G_UNLIKELY (texture_id == 0) + continue; + + if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff) + { + if G_LIKELY (last_texture != 0) + { + guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count; + + /* Since we have batched added our VBO vertices to avoid repeated + * calls to the buffer, we need to manually tweak the vbo offset + * of the new batch as otherwise it will point at the end of our + * vbo array. + */ + gsk_gl_render_job_split_draw (job); + batch = gsk_gl_command_queue_get_batch (job->command_queue); + batch->draw.vbo_offset = vbo_offset; + } + + gsk_gl_program_set_uniform4i (job->current_program, + UNIFORM_GLYPHY_ATLAS_INFO, 0, + GSK_GL_TEXTURE_LIBRARY (library)->atlas_width, + GSK_GL_TEXTURE_LIBRARY (library)->atlas_height, + library->item_w, + library->item_h_q); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_GLYPHY_GAMMA_ADJUST, 0, + 1.0); + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_GLYPHY_CONTRAST, 0, + 1.0); + + last_texture = texture_id; + } + + cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE; + if G_UNLIKELY (gi->geometry.y_offset != 0) + cy = (float)(gi->geometry.y_offset) / PANGO_SCALE; + + x_position += gi->geometry.width; + + EncodedGlyph encoded[4]; +#define ENCODE_CORNER(_cx, _cy) \ + G_STMT_START { \ + float _vx = x + cx + ((1-_cx) * glyph->extents.min_x + _cx * glyph->extents.max_x); \ + float _vy = y + cy - ((1-_cy) * glyph->extents.min_y + _cy * glyph->extents.max_y); \ + encoded_glyph_init (&encoded[_cx * 2 + _cy], _vx, _vy, _cx, _cy, glyph); \ + } G_STMT_END + ENCODE_CORNER (0, 0); + ENCODE_CORNER (0, 1); + ENCODE_CORNER (1, 0); + ENCODE_CORNER (1, 1); +#undef ENCODE_CORNER + + add_encoded_glyph (vertices++, &encoded[0], c); + add_encoded_glyph (vertices++, &encoded[1], c); + add_encoded_glyph (vertices++, &encoded[2], c); + + add_encoded_glyph (vertices++, &encoded[1], c); + add_encoded_glyph (vertices++, &encoded[2], c); + add_encoded_glyph (vertices++, &encoded[3], c); + + batch->draw.vbo_count += GSK_GL_N_VERTICES; + used++; + } + + if (used != num_glyphs) + gsk_gl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used); + + gsk_gl_render_job_end_draw (job); } static inline void @@ -3108,7 +3290,7 @@ gsk_gl_render_job_visit_text_node (GskGLRenderJob *job, gboolean force_color) { if (!gsk_text_node_has_color_glyphs (node)) - gsk_gl_render_job_visit_text_node_glyphy (job, node, color, force_color); + gsk_gl_render_job_visit_text_node_glyphy (job, node, color); else gsk_gl_render_job_visit_text_node_legacy (job, node, color, force_color); } diff --git a/gsk/gl/resources/glyphy.atlas.glsl b/gsk/gl/resources/glyphy.atlas.glsl new file mode 100644 index 0000000000..af5910e46c --- /dev/null +++ b/gsk/gl/resources/glyphy.atlas.glsl @@ -0,0 +1,15 @@ +uniform ivec4 u_atlas_info; + +#define GLYPHY_TEXTURE1D_EXTRA_DECLS , sampler2D _tex, ivec4 _atlas_info, ivec2 _atlas_pos +#define GLYPHY_TEXTURE1D_EXTRA_ARGS , _tex, _atlas_info, _atlas_pos +#define GLYPHY_DEMO_EXTRA_ARGS , u_source, u_atlas_info, gi.atlas_pos + +vec4 +glyphy_texture1D_func (int offset GLYPHY_TEXTURE1D_EXTRA_DECLS) +{ + ivec2 item_geom = _atlas_info.zw; + vec2 pos = (vec2 (_atlas_pos.xy * item_geom + + ivec2 (mod (float (offset), float (item_geom.x)), offset / item_geom.x)) + + + vec2 (.5, .5)) / vec2(_atlas_info.xy); + return GskTexture (_tex, pos); +} diff --git a/gsk/gl/resources/glyphy.fs.glsl b/gsk/gl/resources/glyphy.fs.glsl new file mode 100644 index 0000000000..691ad77b1f --- /dev/null +++ b/gsk/gl/resources/glyphy.fs.glsl @@ -0,0 +1,88 @@ +// FRAGMENT_SHADER: +// glyphy.fs.glsl + +uniform float u_contrast; +uniform float u_gamma_adjust; +uniform float u_outline_thickness; +uniform bool u_outline; +uniform float u_boldness; +uniform bool u_debug; + +_IN_ vec4 v_glyph; +_IN_ vec4 final_color; + +#define SQRT2_2 0.70710678118654757 /* 1 / sqrt(2.) */ +#define SQRT2 1.4142135623730951 + +struct glyph_info_t { + ivec2 nominal_size; + ivec2 atlas_pos; +}; + +glyph_info_t +glyph_info_decode (vec4 v) +{ + glyph_info_t gi; + gi.nominal_size = (ivec2 (mod (v.zw, 256.)) + 2) / 4; + gi.atlas_pos = ivec2 (v_glyph.zw) / 256; + return gi; +} + + +float +antialias (float d) +{ + return smoothstep (-.75, +.75, d); +} + +void +main() +{ + vec2 p = v_glyph.xy; + glyph_info_t gi = glyph_info_decode (v_glyph); + + /* isotropic antialiasing */ + vec2 dpdx = dFdx (p); + vec2 dpdy = dFdy (p); + float m = length (vec2 (length (dpdx), length (dpdy))) * SQRT2_2; + + vec4 color = final_color; + + float gsdist = glyphy_sdf (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); + float sdist = gsdist / m * u_contrast; + + if (!u_debug) { + sdist -= u_boldness * 10.; + if (u_outline) + sdist = abs (sdist) - u_outline_thickness * .5; + if (sdist > 1.) + discard; + float alpha = antialias (-sdist); + if (u_gamma_adjust != 1.) + alpha = pow (alpha, 1./u_gamma_adjust); + color = vec4 (color.rgb,color.a * alpha); + } else { + color = vec4 (0,0,0,0); + + // Color the inside of the glyph a light red + color += vec4 (.5,0,0,.5) * smoothstep (1., -1., sdist); + + float udist = abs (sdist); + float gudist = abs (gsdist); + // Color the outline red + color += vec4 (1,0,0,1) * smoothstep (2., 1., udist); + // Color the distance field in green + if (!glyphy_isinf (udist)) + color += vec4(0,.4,0,.4 - (abs(gsdist) / max(float(gi.nominal_size.x), float(gi.nominal_size.y))) * 4.); + + float pdist = glyphy_point_dist (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); + // Color points green + color = mix (vec4 (0,1,0,.5), color, smoothstep (.05, .06, pdist)); + + glyphy_arc_list_t arc_list = glyphy_arc_list (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); + // Color the number of endpoints per cell blue + color += vec4 (0,0,1,.1) * float(arc_list.num_endpoints) * 32./255.; + } + + gskSetOutputColor(color); +} diff --git a/gsk/gl/resources/glyphy.vs.glsl b/gsk/gl/resources/glyphy.vs.glsl new file mode 100644 index 0000000000..f8323dd78a --- /dev/null +++ b/gsk/gl/resources/glyphy.vs.glsl @@ -0,0 +1,25 @@ +// VERTEX_SHADER: +// glyphy.vs.glsl + +_OUT_ vec4 v_glyph; +_OUT_ vec4 final_color; + +// Keep this in sync with glyph_encode in gskglrenderjob.c +vec4 +glyph_vertex_transcode (vec2 v) +{ + ivec2 g = ivec2 (v); + ivec2 corner = ivec2 (mod (v, 2.)); + g /= 2; + ivec2 nominal_size = ivec2 (mod (vec2(g), 64.)); + return vec4 (corner * nominal_size, g * 4); +} + +void +main() +{ + v_glyph = glyph_vertex_transcode(aUv); + vUv = v_glyph.zw; + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + final_color = gsk_scaled_premultiply(aColor, u_alpha); +} diff --git a/gsk/meson.build b/gsk/meson.build index eb904b4988..3b09697610 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -20,6 +20,9 @@ gsk_private_gl_shaders = [ 'gl/resources/custom.glsl', 'gl/resources/filled_border.glsl', 'gl/resources/mask.glsl', + 'gl/resources/glyphy.atlas.glsl', + 'gl/resources/glyphy.fs.glsl', + 'gl/resources/glyphy.vs.glsl', ] gsk_public_sources = files([