From 0ca8b62306ec4c8b97ed42d8d239b7fc1f992e2d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:25:58 -0600 Subject: [PATCH 01/68] Replace arc API with quadratic Bezier curve accumulator Strip all arc, SDF, blob encoding, and shader API from glyphy.h. Add glyphy_curve_t (quadratic Bezier with p1/p2/p3) and glyphy_curve_accumulator_t that directly stores quadratic curves from glyph outlines without arc approximation. This is the first step toward Slug-style winding number rendering. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-curves.cc | 207 +++++++++++++++++++++++++++++++++ src/glyphy.h | 271 ++++++------------------------------------- 2 files changed, 240 insertions(+), 238 deletions(-) create mode 100644 src/glyphy-curves.cc diff --git a/src/glyphy-curves.cc b/src/glyphy-curves.cc new file mode 100644 index 0000000..72246cf --- /dev/null +++ b/src/glyphy-curves.cc @@ -0,0 +1,207 @@ +/* + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "glyphy.h" + +#include + + +/* + * Accumulate quadratic Bezier curves from glyph outlines + */ + + +struct glyphy_curve_accumulator_t { + unsigned int refcount; + + glyphy_curve_accumulator_callback_t callback; + void *user_data; + + glyphy_point_t start_point; + glyphy_point_t current_point; + bool need_moveto; + unsigned int num_curves; + glyphy_bool_t success; +}; + + +glyphy_curve_accumulator_t * +glyphy_curve_accumulator_create (void) +{ + glyphy_curve_accumulator_t *acc = (glyphy_curve_accumulator_t *) calloc (1, sizeof (glyphy_curve_accumulator_t)); + acc->refcount = 1; + + acc->callback = NULL; + acc->user_data = NULL; + + glyphy_curve_accumulator_reset (acc); + + return acc; +} + +void +glyphy_curve_accumulator_reset (glyphy_curve_accumulator_t *acc) +{ + acc->start_point = acc->current_point = {0, 0}; + acc->need_moveto = true; + acc->num_curves = 0; + acc->success = true; +} + +void +glyphy_curve_accumulator_destroy (glyphy_curve_accumulator_t *acc) +{ + if (!acc || --acc->refcount) + return; + + free (acc); +} + +glyphy_curve_accumulator_t * +glyphy_curve_accumulator_reference (glyphy_curve_accumulator_t *acc) +{ + if (acc) + acc->refcount++; + return acc; +} + + +/* Configure acc */ + +void +glyphy_curve_accumulator_set_callback (glyphy_curve_accumulator_t *acc, + glyphy_curve_accumulator_callback_t callback, + void *user_data) +{ + acc->callback = callback; + acc->user_data = user_data; +} + +void +glyphy_curve_accumulator_get_callback (glyphy_curve_accumulator_t *acc, + glyphy_curve_accumulator_callback_t *callback, + void **user_data) +{ + *callback = acc->callback; + *user_data = acc->user_data; +} + + +/* Accumulation results */ + +unsigned int +glyphy_curve_accumulator_get_num_curves (glyphy_curve_accumulator_t *acc) +{ + return acc->num_curves; +} + +glyphy_bool_t +glyphy_curve_accumulator_successful (glyphy_curve_accumulator_t *acc) +{ + return acc->success; +} + + +/* Accumulate */ + +static void +emit (glyphy_curve_accumulator_t *acc, const glyphy_curve_t *curve) +{ + acc->success = acc->success && acc->callback (curve, acc->user_data); + if (acc->success) { + acc->num_curves++; + acc->current_point = curve->p3; + } +} + +static void +emit_conic (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p2, + const glyphy_point_t *p3) +{ + if (acc->current_point.x == p3->x && acc->current_point.y == p3->y) + return; + + if (acc->need_moveto) { + acc->start_point = acc->current_point; + acc->need_moveto = false; + } + + glyphy_curve_t curve = {acc->current_point, *p2, *p3}; + emit (acc, &curve); +} + + +void +glyphy_curve_accumulator_move_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p0) +{ + acc->need_moveto = true; + acc->current_point = *p0; +} + +void +glyphy_curve_accumulator_line_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p1) +{ + /* Line as degenerate quadratic: p2 = midpoint(p1, p3) */ + glyphy_point_t mid = { + (acc->current_point.x + p1->x) * 0.5, + (acc->current_point.y + p1->y) * 0.5 + }; + emit_conic (acc, &mid, p1); +} + +void +glyphy_curve_accumulator_conic_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p1, + const glyphy_point_t *p2) +{ + emit_conic (acc, p1, p2); +} + +void +glyphy_curve_accumulator_close_path (glyphy_curve_accumulator_t *acc) +{ + if (!acc->need_moveto && + (acc->current_point.x != acc->start_point.x || + acc->current_point.y != acc->start_point.y)) + glyphy_curve_accumulator_line_to (acc, &acc->start_point); +} + + +/* + * Curve list extents + */ + +void +glyphy_curve_list_extents (const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_extents_t *extents) +{ + glyphy_extents_clear (extents); + for (unsigned int i = 0; i < num_curves; i++) { + const glyphy_curve_t &c = curves[i]; + /* Conservative: use control point bounding box */ + glyphy_extents_add (extents, &c.p1); + glyphy_extents_add (extents, &c.p2); + glyphy_extents_add (extents, &c.p3); + } +} diff --git a/src/glyphy.h b/src/glyphy.h index 8d35279..4dc3562 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -83,289 +83,84 @@ glyphy_extents_scale (glyphy_extents_t *extents, /* - * Circular arcs + * Quadratic Bezier curves */ typedef struct { - glyphy_point_t p0; glyphy_point_t p1; - double d; -} glyphy_arc_t; - - -/* Build from a conventional arc representation */ -GLYPHY_API void -glyphy_arc_from_conventional (const glyphy_point_t *center, - double radius, - double angle0, - double angle1, - glyphy_bool_t negative, - glyphy_arc_t *arc); - -/* Convert to a conventional arc representation */ -GLYPHY_API void -glyphy_arc_to_conventional (glyphy_arc_t arc, - glyphy_point_t *center /* may be NULL */, - double *radius /* may be NULL */, - double *angle0 /* may be NULL */, - double *angle1 /* may be NULL */, - glyphy_bool_t *negative /* may be NULL */); - -GLYPHY_API glyphy_bool_t -glyphy_arc_is_a_line (glyphy_arc_t arc); - -GLYPHY_API void -glyphy_arc_extents (glyphy_arc_t arc, - glyphy_extents_t *extents); - + glyphy_point_t p2; + glyphy_point_t p3; +} glyphy_curve_t; /* - * Approximate single pieces of geometry to/from one arc + * Accumulate quadratic Bezier curves from glyph outlines */ -GLYPHY_API void -glyphy_arc_from_line (const glyphy_point_t *p0, - const glyphy_point_t *p1, - glyphy_arc_t *arc); +typedef glyphy_bool_t (*glyphy_curve_accumulator_callback_t) (const glyphy_curve_t *curve, + void *user_data); -GLYPHY_API void -glyphy_arc_from_conic (const glyphy_point_t *p0, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - glyphy_arc_t *arc, - double *error); +typedef struct glyphy_curve_accumulator_t glyphy_curve_accumulator_t; -GLYPHY_API void -glyphy_arc_from_cubic (const glyphy_point_t *p0, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - const glyphy_point_t *p3, - glyphy_arc_t *arc, - double *error); +GLYPHY_API glyphy_curve_accumulator_t * +glyphy_curve_accumulator_create (void); GLYPHY_API void -glyphy_arc_to_cubic (const glyphy_arc_t *arc, - glyphy_point_t *p0, - glyphy_point_t *p1, - glyphy_point_t *p2, - glyphy_point_t *p3, - double *error); - - - -/* - * Approximate outlines with multiple arcs - */ - - -typedef struct { - glyphy_point_t p; - double d; -} glyphy_arc_endpoint_t; - -typedef glyphy_bool_t (*glyphy_arc_endpoint_accumulator_callback_t) (glyphy_arc_endpoint_t *endpoint, - void *user_data); - - -typedef struct glyphy_arc_accumulator_t glyphy_arc_accumulator_t; - -GLYPHY_API glyphy_arc_accumulator_t * -glyphy_arc_accumulator_create (void); - -GLYPHY_API void -glyphy_arc_accumulator_destroy (glyphy_arc_accumulator_t *acc); - -GLYPHY_API glyphy_arc_accumulator_t * -glyphy_arc_accumulator_reference (glyphy_arc_accumulator_t *acc); +glyphy_curve_accumulator_destroy (glyphy_curve_accumulator_t *acc); +GLYPHY_API glyphy_curve_accumulator_t * +glyphy_curve_accumulator_reference (glyphy_curve_accumulator_t *acc); GLYPHY_API void -glyphy_arc_accumulator_reset (glyphy_arc_accumulator_t *acc); +glyphy_curve_accumulator_reset (glyphy_curve_accumulator_t *acc); /* Configure accumulator */ GLYPHY_API void -glyphy_arc_accumulator_set_tolerance (glyphy_arc_accumulator_t *acc, - double tolerance); - -GLYPHY_API double -glyphy_arc_accumulator_get_tolerance (glyphy_arc_accumulator_t *acc); - -GLYPHY_API void -glyphy_arc_accumulator_set_callback (glyphy_arc_accumulator_t *acc, - glyphy_arc_endpoint_accumulator_callback_t callback, - void *user_data); - -GLYPHY_API void -glyphy_arc_accumulator_get_callback (glyphy_arc_accumulator_t *acc, - glyphy_arc_endpoint_accumulator_callback_t *callback, - void **user_data); - -GLYPHY_API void -glyphy_arc_accumulator_set_d_metrics (glyphy_arc_accumulator_t *acc, - double max_d, - double d_bits); +glyphy_curve_accumulator_set_callback (glyphy_curve_accumulator_t *acc, + glyphy_curve_accumulator_callback_t callback, + void *user_data); GLYPHY_API void -glyphy_arc_accumulator_get_d_metrics (glyphy_arc_accumulator_t *acc, - double *max_d, - double *d_bits); +glyphy_curve_accumulator_get_callback (glyphy_curve_accumulator_t *acc, + glyphy_curve_accumulator_callback_t *callback, + void **user_data); /* Accumulation results */ GLYPHY_API unsigned int -glyphy_arc_accumulator_get_num_endpoints (glyphy_arc_accumulator_t *acc); - -GLYPHY_API double -glyphy_arc_accumulator_get_error (glyphy_arc_accumulator_t *acc); +glyphy_curve_accumulator_get_num_curves (glyphy_curve_accumulator_t *acc); GLYPHY_API glyphy_bool_t -glyphy_arc_accumulator_successful (glyphy_arc_accumulator_t *acc); +glyphy_curve_accumulator_successful (glyphy_curve_accumulator_t *acc); /* Accumulate */ GLYPHY_API void -glyphy_arc_accumulator_move_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p0); - -GLYPHY_API void -glyphy_arc_accumulator_line_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1); - -GLYPHY_API void -glyphy_arc_accumulator_conic_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - const glyphy_point_t *p2); +glyphy_curve_accumulator_move_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p0); GLYPHY_API void -glyphy_arc_accumulator_cubic_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - const glyphy_point_t *p3); +glyphy_curve_accumulator_line_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p1); GLYPHY_API void -glyphy_arc_accumulator_arc_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - double d); +glyphy_curve_accumulator_conic_to (glyphy_curve_accumulator_t *acc, + const glyphy_point_t *p1, + const glyphy_point_t *p2); GLYPHY_API void -glyphy_arc_accumulator_close_path (glyphy_arc_accumulator_t *acc); +glyphy_curve_accumulator_close_path (glyphy_curve_accumulator_t *acc); GLYPHY_API void -glyphy_arc_list_extents (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_extents_t *extents); - - - -/* - * Modify outlines for proper consumption - */ - -GLYPHY_API void -glyphy_outline_reverse (glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints); - -/* Returns true if outline was modified */ -GLYPHY_API glyphy_bool_t -glyphy_outline_winding_from_even_odd (glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_bool_t inverse); - - - -/* - * Encode an arc outline into binary blob for fast SDF calculation - */ - - -typedef struct { - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; -} glyphy_rgba_t; - - -/* TODO make this callback-based also? */ -/* TODO rename to glyphy_blob_encode? */ -GLYPHY_API glyphy_bool_t -glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_rgba_t *blob, - unsigned int blob_size, - double faraway, - double avg_fetch_desired, - double *avg_fetch_achieved, - unsigned int *output_len, - unsigned int *nominal_width, /* 6bit */ - unsigned int *nominal_height, /* 6bit */ - glyphy_extents_t *pextents); - -GLYPHY_API glyphy_bool_t -glyphy_arc_list_encode_blob2 (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_rgba_t *blob, - unsigned int blob_size, - double faraway, - double grid_unit, - double enlighten_max, - double embolden_max, - double *avg_fetch_achieved, - unsigned int *output_len, - unsigned int *nominal_width, /* 6bit */ - unsigned int *nominal_height, /* 6bit */ - glyphy_extents_t *pextents); - -/* TBD _decode_blob */ - - - -/* - * Calculate signed-distance-field from (encoded) arc list - */ - - -GLYPHY_API double -glyphy_sdf_from_arc_list (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - const glyphy_point_t *p, - glyphy_point_t *closest_p /* may be NULL; TBD not implemented yet */); - -/* TBD */ -GLYPHY_API double -glyphy_sdf_from_blob (const glyphy_rgba_t *blob, - unsigned int nominal_width, - unsigned int nominal_height, - const glyphy_point_t *p, - glyphy_point_t *closest_p /* may be NULL; TBD not implemented yet */); - - - -/* - * Shader source code - */ - - -/* TODO make this enum-based? */ - -GLYPHY_API const char * -glyphy_common_shader_source (void); - -GLYPHY_API const char * -glyphy_common_shader_source_path (void); - -GLYPHY_API const char * -glyphy_sdf_shader_source (void); - -GLYPHY_API const char * -glyphy_sdf_shader_source_path (void); +glyphy_curve_list_extents (const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_extents_t *extents); #ifdef __cplusplus From 2d881339d77271bce08c4f59de4f9c65aef4ef6e Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:30:22 -0600 Subject: [PATCH 02/68] Add band builder and blob encoder for Slug-style rendering Encodes quadratic Bezier curves into a RGBA16UI blob with: - Horizontal and vertical bands for spatial indexing - Curves sorted by descending max coordinate for early exit - Em-space quantized int16 coordinates (configurable scale) - Horizontal/vertical line filtering (can't intersect parallel rays) Blob layout: [band headers] [curve index lists] [curve data] All offsets are 1D from blob start; shader converts to 2D via atlas width. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 262 +++++++++++++++++++++++++++++++++++++++++++ src/glyphy.h | 28 +++++ 2 files changed, 290 insertions(+) create mode 100644 src/glyphy-encode.cc diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc new file mode 100644 index 0000000..2925ef3 --- /dev/null +++ b/src/glyphy-encode.cc @@ -0,0 +1,262 @@ +/* + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "glyphy.h" + +#include +#include +#include +#include + + +/* + * Encode quadratic Bezier curves into a blob for GPU rendering. + * + * Blob layout (single RGBA16UI texture region): + * + * [H-band headers (num_hbands texels)] + * [V-band headers (num_vbands texels)] + * [Curve index lists (variable)] + * [Curve data (2 texels per curve)] + * + * Band header texel: + * R = curve count + * G = offset to curve index list (from blob start) + * B = reserved (split value for symmetric bands, future) + * A = reserved + * + * Curve index texel: + * R = offset to curve data (from blob start) + * G = reserved + * B = reserved + * A = reserved + * + * Curve data (2 consecutive texels): + * Texel 0: R=p1.x, G=p1.y, B=p2.x, A=p2.y (int16, em-space * UNITS_PER_EM_UNIT) + * Texel 1: R=p3.x, G=p3.y, B=0, A=0 + * + * All offsets are 1D from blob start. The shader converts to 2D atlas + * coordinates using the atlas width, similar to Slug's CalcBandLoc. + */ + + +static int16_t +quantize (double v) +{ + return (int16_t) round (v * GLYPHY_UNITS_PER_EM_UNIT); +} + +static double +curve_max_x (const glyphy_curve_t *c) +{ + return fmax (fmax (c->p1.x, c->p2.x), c->p3.x); +} + +static double +curve_max_y (const glyphy_curve_t *c) +{ + return fmax (fmax (c->p1.y, c->p2.y), c->p3.y); +} + +static void +curve_y_range (const glyphy_curve_t *c, double *min_y, double *max_y) +{ + *min_y = fmin (fmin (c->p1.y, c->p2.y), c->p3.y); + *max_y = fmax (fmax (c->p1.y, c->p2.y), c->p3.y); +} + +static void +curve_x_range (const glyphy_curve_t *c, double *min_x, double *max_x) +{ + *min_x = fmin (fmin (c->p1.x, c->p2.x), c->p3.x); + *max_x = fmax (fmax (c->p1.x, c->p2.x), c->p3.x); +} + +static bool +curve_is_horizontal (const glyphy_curve_t *c) +{ + return c->p1.y == c->p2.y && c->p2.y == c->p3.y; +} + +static bool +curve_is_vertical (const glyphy_curve_t *c) +{ + return c->p1.x == c->p2.x && c->p2.x == c->p3.x; +} + + +glyphy_bool_t +glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_texel_t *blob, + unsigned int blob_size, + unsigned int *output_len, + unsigned int *p_num_hbands, + unsigned int *p_num_vbands, + glyphy_extents_t *extents) +{ + glyphy_curve_list_extents (curves, num_curves, extents); + + if (num_curves == 0) { + *output_len = 0; + *p_num_hbands = 0; + *p_num_vbands = 0; + return true; + } + + /* Choose number of bands */ + unsigned int num_hbands = std::min (num_curves, 32u); + unsigned int num_vbands = std::min (num_curves, 32u); + num_hbands = std::max (num_hbands, 1u); + num_vbands = std::max (num_vbands, 1u); + + double height = extents->max_y - extents->min_y; + double width = extents->max_x - extents->min_x; + + if (height <= 0) num_hbands = 1; + if (width <= 0) num_vbands = 1; + + double hband_size = height / num_hbands; + double vband_size = width / num_vbands; + + /* Assign curves to bands */ + std::vector> hband_curves (num_hbands); + std::vector> vband_curves (num_vbands); + + for (unsigned int i = 0; i < num_curves; i++) { + /* Horizontal lines never intersect horizontal rays; + * vertical lines never intersect vertical rays. */ + + if (!curve_is_horizontal (&curves[i])) { + if (height > 0) { + double min_y, max_y; + curve_y_range (&curves[i], &min_y, &max_y); + int band_lo = (int) ((min_y - extents->min_y) / hband_size); + int band_hi = (int) ((max_y - extents->min_y) / hband_size); + band_lo = std::max (band_lo, 0); + band_hi = std::min (band_hi, (int) num_hbands - 1); + for (int b = band_lo; b <= band_hi; b++) + hband_curves[b].push_back (i); + } else { + hband_curves[0].push_back (i); + } + } + + if (!curve_is_vertical (&curves[i])) { + if (width > 0) { + double min_x, max_x; + curve_x_range (&curves[i], &min_x, &max_x); + int band_lo = (int) ((min_x - extents->min_x) / vband_size); + int band_hi = (int) ((max_x - extents->min_x) / vband_size); + band_lo = std::max (band_lo, 0); + band_hi = std::min (band_hi, (int) num_vbands - 1); + for (int b = band_lo; b <= band_hi; b++) + vband_curves[b].push_back (i); + } else { + vband_curves[0].push_back (i); + } + } + } + + /* Sort curves within bands for early exit. + * H-bands: descending max-x (rightward ray exits early). + * V-bands: descending max-y (upward ray exits early). */ + for (auto &band : hband_curves) + std::sort (band.begin (), band.end (), + [&] (unsigned int a, unsigned int b) { + return curve_max_x (&curves[a]) > curve_max_x (&curves[b]); + }); + + for (auto &band : vband_curves) + std::sort (band.begin (), band.end (), + [&] (unsigned int a, unsigned int b) { + return curve_max_y (&curves[a]) > curve_max_y (&curves[b]); + }); + + /* Compute sizes */ + unsigned int total_curve_indices = 0; + for (auto &band : hband_curves) total_curve_indices += band.size (); + for (auto &band : vband_curves) total_curve_indices += band.size (); + + unsigned int band_headers_len = num_hbands + num_vbands; + unsigned int curve_data_len = num_curves * 2; + unsigned int total_len = band_headers_len + total_curve_indices + curve_data_len; + + if (total_len > blob_size) + return false; + + unsigned int curve_data_offset = band_headers_len + total_curve_indices; + + /* Pack curve data */ + for (unsigned int i = 0; i < num_curves; i++) { + unsigned int off = curve_data_offset + i * 2; + blob[off].r = (uint16_t) quantize (curves[i].p1.x); + blob[off].g = (uint16_t) quantize (curves[i].p1.y); + blob[off].b = (uint16_t) quantize (curves[i].p2.x); + blob[off].a = (uint16_t) quantize (curves[i].p2.y); + + blob[off + 1].r = (uint16_t) quantize (curves[i].p3.x); + blob[off + 1].g = (uint16_t) quantize (curves[i].p3.y); + blob[off + 1].b = 0; + blob[off + 1].a = 0; + } + + /* Pack band headers and curve indices */ + unsigned int index_offset = band_headers_len; + + for (unsigned int b = 0; b < num_hbands; b++) { + blob[b].r = (uint16_t) hband_curves[b].size (); + blob[b].g = (uint16_t) index_offset; + blob[b].b = 0; + blob[b].a = 0; + + for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { + unsigned int curve_off = curve_data_offset + hband_curves[b][ci] * 2; + blob[index_offset].r = (uint16_t) curve_off; + blob[index_offset].g = 0; + blob[index_offset].b = 0; + blob[index_offset].a = 0; + index_offset++; + } + } + + for (unsigned int b = 0; b < num_vbands; b++) { + unsigned int header_off = num_hbands + b; + blob[header_off].r = (uint16_t) vband_curves[b].size (); + blob[header_off].g = (uint16_t) index_offset; + blob[header_off].b = 0; + blob[header_off].a = 0; + + for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { + unsigned int curve_off = curve_data_offset + vband_curves[b][ci] * 2; + blob[index_offset].r = (uint16_t) curve_off; + blob[index_offset].g = 0; + blob[index_offset].b = 0; + blob[index_offset].a = 0; + index_offset++; + } + } + + *output_len = total_len; + *p_num_hbands = num_hbands; + *p_num_vbands = num_vbands; + + return true; +} diff --git a/src/glyphy.h b/src/glyphy.h index 4dc3562..384c626 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -163,6 +163,34 @@ glyphy_curve_list_extents (const glyphy_curve_t *curves, glyphy_extents_t *extents); + +/* + * Encode curves into blob for GPU rendering + */ + + +#ifndef GLYPHY_UNITS_PER_EM_UNIT +#define GLYPHY_UNITS_PER_EM_UNIT 2 +#endif + +typedef struct { + unsigned short r; + unsigned short g; + unsigned short b; + unsigned short a; +} glyphy_texel_t; + +GLYPHY_API glyphy_bool_t +glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_texel_t *blob, + unsigned int blob_size, + unsigned int *output_len, + unsigned int *num_hbands, + unsigned int *num_vbands, + glyphy_extents_t *extents); + + #ifdef __cplusplus } #endif From bd082fb2c2564a543f962fa6f5aadb30a092551f Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:35:12 -0600 Subject: [PATCH 03/68] Add Slug pixel shader (GLSL 1.30 port) and update build Port SlugPixelShader.hlsl to GLSL 1.30 for our single isampler2D atlas: - CalcRootCode: equivalence class via sign-bit extraction (floatBitsToUint) - SolveHorizPoly/SolveVertPoly: quadratic root finding with discriminant clamping - CalcCoverage: weighted combination of horizontal and vertical ray coverage - glyphy_slug_render: main entry point, iterates H-bands and V-bands Adapted for single-texture blob layout with 1D offsets via CalcBandLoc. Curve data decoded from int16 quantized em-space coordinates. Also: cap bands at 16, use signed int16 texels, update meson.build to remove old arc/sdf/blob sources and add new files. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 30 +++--- src/glyphy-shaders.cc | 16 +-- src/glyphy-slug.glsl | 220 ++++++++++++++++++++++++++++++++++++++++++ src/glyphy.h | 19 +++- src/meson.build | 14 +-- 5 files changed, 258 insertions(+), 41 deletions(-) create mode 100644 src/glyphy-slug.glsl diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 2925ef3..aba17fb 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -121,9 +121,9 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, return true; } - /* Choose number of bands */ - unsigned int num_hbands = std::min (num_curves, 32u); - unsigned int num_vbands = std::min (num_curves, 32u); + /* Choose number of bands (capped at 16 per Slug paper) */ + unsigned int num_hbands = std::min (num_curves, 16u); + unsigned int num_vbands = std::min (num_curves, 16u); num_hbands = std::max (num_hbands, 1u); num_vbands = std::max (num_vbands, 1u); @@ -207,13 +207,13 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, /* Pack curve data */ for (unsigned int i = 0; i < num_curves; i++) { unsigned int off = curve_data_offset + i * 2; - blob[off].r = (uint16_t) quantize (curves[i].p1.x); - blob[off].g = (uint16_t) quantize (curves[i].p1.y); - blob[off].b = (uint16_t) quantize (curves[i].p2.x); - blob[off].a = (uint16_t) quantize (curves[i].p2.y); + blob[off].r = (int16_t) quantize (curves[i].p1.x); + blob[off].g = (int16_t) quantize (curves[i].p1.y); + blob[off].b = (int16_t) quantize (curves[i].p2.x); + blob[off].a = (int16_t) quantize (curves[i].p2.y); - blob[off + 1].r = (uint16_t) quantize (curves[i].p3.x); - blob[off + 1].g = (uint16_t) quantize (curves[i].p3.y); + blob[off + 1].r = (int16_t) quantize (curves[i].p3.x); + blob[off + 1].g = (int16_t) quantize (curves[i].p3.y); blob[off + 1].b = 0; blob[off + 1].a = 0; } @@ -222,14 +222,14 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int index_offset = band_headers_len; for (unsigned int b = 0; b < num_hbands; b++) { - blob[b].r = (uint16_t) hband_curves[b].size (); - blob[b].g = (uint16_t) index_offset; + blob[b].r = (int16_t) hband_curves[b].size (); + blob[b].g = (int16_t) index_offset; blob[b].b = 0; blob[b].a = 0; for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { unsigned int curve_off = curve_data_offset + hband_curves[b][ci] * 2; - blob[index_offset].r = (uint16_t) curve_off; + blob[index_offset].r = (int16_t) curve_off; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; @@ -239,14 +239,14 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, for (unsigned int b = 0; b < num_vbands; b++) { unsigned int header_off = num_hbands + b; - blob[header_off].r = (uint16_t) vband_curves[b].size (); - blob[header_off].g = (uint16_t) index_offset; + blob[header_off].r = (int16_t) vband_curves[b].size (); + blob[header_off].g = (int16_t) index_offset; blob[header_off].b = 0; blob[header_off].a = 0; for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { unsigned int curve_off = curve_data_offset + vband_curves[b][ci] * 2; - blob[index_offset].r = (uint16_t) curve_off; + blob[index_offset].r = (int16_t) curve_off; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; diff --git a/src/glyphy-shaders.cc b/src/glyphy-shaders.cc index c06b73e..253c06a 100644 --- a/src/glyphy-shaders.cc +++ b/src/glyphy-shaders.cc @@ -1,5 +1,5 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,28 +12,22 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski */ #ifdef HAVE_CONFIG_H #include #endif -#include "glyphy-common.hh" +#include "glyphy.h" /* * Shader source code */ -/* TODO path separator */ #define SHADER_PATH(File) PKGDATADIR "/" File -#include "glyphy-common-glsl.h" -#include "glyphy-sdf-glsl.h" +#include "glyphy-slug-glsl.h" -const char * glyphy_common_shader_source (void) { return glyphy_common_glsl; } -const char * glyphy_sdf_shader_source (void) { return glyphy_sdf_glsl; } +const char * glyphy_slug_shader_source (void) { return glyphy_slug_glsl; } -const char * glyphy_common_shader_source_path (void) { return SHADER_PATH ("glyphy-common.glsl"); } -const char * glyphy_sdf_shader_source_path (void) { return SHADER_PATH ("glyphy-sdf.glsl"); } +const char * glyphy_slug_shader_source_path (void) { return SHADER_PATH ("glyphy-slug.glsl"); } diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl new file mode 100644 index 0000000..4af923c --- /dev/null +++ b/src/glyphy-slug.glsl @@ -0,0 +1,220 @@ +/* + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. + * Based on the Slug algorithm by Eric Lengyel (MIT license). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +/* Requires GLSL 1.30 / ES 3.00 */ + + +#ifndef GLYPHY_ATLAS_WIDTH +#define GLYPHY_ATLAS_WIDTH 4096 +#endif + +#ifndef GLYPHY_UNITS_PER_EM_UNIT +#define GLYPHY_UNITS_PER_EM_UNIT 2 +#endif + +#define GLYPHY_INV_UNITS float(1.0 / float(GLYPHY_UNITS_PER_EM_UNIT)) + +#define GLYPHY_LOG_ATLAS_WIDTH 12 /* log2(4096) */ + + +uniform isampler2D u_atlas; + + +ivec2 glyphy_calc_blob_loc (ivec2 glyphLoc, int offset) +{ + ivec2 loc = ivec2 (glyphLoc.x + offset, glyphLoc.y); + loc.y += loc.x >> GLYPHY_LOG_ATLAS_WIDTH; + loc.x &= (1 << GLYPHY_LOG_ATLAS_WIDTH) - 1; + return loc; +} + +uint glyphy_calc_root_code (float y1, float y2, float y3) +{ + /* Extract sign bits of the three y coordinates. + * This classifies the curve into one of 8 equivalence classes + * that determine which roots contribute to the winding number. */ + + uint i1 = floatBitsToUint (y1) >> 31U; + uint i2 = floatBitsToUint (y2) >> 30U; + uint i3 = floatBitsToUint (y3) >> 29U; + + uint shift = (i2 & 2U) | (i1 & ~2U); + shift = (i3 & 4U) | (shift & ~4U); + + /* Eligibility returned in bits 0 and 8. */ + return (0x2E74U >> shift) & 0x0101U; +} + +vec2 glyphy_solve_horiz_poly (vec4 p12, vec2 p3) +{ + /* Solve for t where curve crosses y = 0. + * Quadratic: a*t^2 - 2*b*t + c = 0 + * Discriminant clamped to zero for robustness. */ + + vec2 a = p12.xy - p12.zw * 2.0 + p3; + vec2 b = p12.xy - p12.zw; + float ra = 1.0 / a.y; + float rb = 0.5 / b.y; + + float d = sqrt (max (b.y * b.y - a.y * p12.y, 0.0)); + float t1 = (b.y - d) * ra; + float t2 = (b.y + d) * ra; + + /* Nearly linear case. */ + if (abs (a.y) < 1.0 / 65536.0) + t1 = t2 = p12.y * rb; + + /* Return x coordinates at the roots. */ + return vec2 ((a.x * t1 - b.x * 2.0) * t1 + p12.x, + (a.x * t2 - b.x * 2.0) * t2 + p12.x); +} + +vec2 glyphy_solve_vert_poly (vec4 p12, vec2 p3) +{ + /* Solve for t where curve crosses x = 0. */ + + vec2 a = p12.xy - p12.zw * 2.0 + p3; + vec2 b = p12.xy - p12.zw; + float ra = 1.0 / a.x; + float rb = 0.5 / b.x; + + float d = sqrt (max (b.x * b.x - a.x * p12.x, 0.0)); + float t1 = (b.x - d) * ra; + float t2 = (b.x + d) * ra; + + if (abs (a.x) < 1.0 / 65536.0) + t1 = t2 = p12.x * rb; + + return vec2 ((a.y * t1 - b.y * 2.0) * t1 + p12.y, + (a.y * t2 - b.y * 2.0) * t2 + p12.y); +} + +float glyphy_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) +{ + /* Combine horizontal and vertical ray coverages. */ + + float coverage = max (abs (xcov * xwgt + ycov * ywgt) / + max (xwgt + ywgt, 1.0 / 65536.0), + min (abs (xcov), abs (ycov))); + + /* Nonzero fill rule. */ + return clamp (coverage, 0.0, 1.0); +} + +float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, + ivec2 glyphLoc, int numHBands, int numVBands) +{ + vec2 emsPerPixel = fwidth (renderCoord); + vec2 pixelsPerEm = 1.0 / emsPerPixel; + + /* Map em-space coordinates to band indices. */ + ivec2 bandIndex = clamp (ivec2 (renderCoord * bandTransform.xy + bandTransform.zw), + ivec2 (0, 0), + ivec2 (numVBands - 1, numHBands - 1)); + + float xcov = 0.0; + float xwgt = 0.0; + + /* Fetch H-band header. H-bands are at offsets [0, numHBands-1]. */ + ivec4 hbandData = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, bandIndex.y), 0); + int hCurveCount = hbandData.r; + int hDataOffset = hbandData.g; + + /* Loop over curves in the horizontal band. */ + for (int ci = 0; ci < hCurveCount; ci++) + { + /* Fetch curve offset from index list. */ + ivec4 indexData = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, hDataOffset + ci), 0); + int curveOffset = indexData.r; + + /* Fetch control points and convert from quantized int16 to em-space. */ + ivec4 raw12 = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, curveOffset), 0); + ivec4 raw3 = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, curveOffset + 1), 0); + + vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); + vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; + + /* Early exit: if max x of all control points is left of pixel. */ + if (max (max (p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) + break; + + uint code = glyphy_calc_root_code (p12.y, p12.w, p3.y); + if (code != 0U) + { + vec2 r = glyphy_solve_horiz_poly (p12, p3) * pixelsPerEm.x; + + if ((code & 1U) != 0U) + { + xcov += clamp (r.x + 0.5, 0.0, 1.0); + xwgt = max (xwgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); + } + + if (code > 1U) + { + xcov -= clamp (r.y + 0.5, 0.0, 1.0); + xwgt = max (xwgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); + } + } + } + + float ycov = 0.0; + float ywgt = 0.0; + + /* Fetch V-band header. V-bands start at offset numHBands. */ + ivec4 vbandData = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, numHBands + bandIndex.x), 0); + int vCurveCount = vbandData.r; + int vDataOffset = vbandData.g; + + /* Loop over curves in the vertical band. */ + for (int ci = 0; ci < vCurveCount; ci++) + { + ivec4 indexData = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, vDataOffset + ci), 0); + int curveOffset = indexData.r; + + ivec4 raw12 = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, curveOffset), 0); + ivec4 raw3 = texelFetch (u_atlas, + glyphy_calc_blob_loc (glyphLoc, curveOffset + 1), 0); + + vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); + vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; + + if (max (max (p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) + break; + + uint code = glyphy_calc_root_code (p12.x, p12.z, p3.x); + if (code != 0U) + { + vec2 r = glyphy_solve_vert_poly (p12, p3) * pixelsPerEm.y; + + if ((code & 1U) != 0U) + { + ycov -= clamp (r.x + 0.5, 0.0, 1.0); + ywgt = max (ywgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); + } + + if (code > 1U) + { + ycov += clamp (r.y + 0.5, 0.0, 1.0); + ywgt = max (ywgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); + } + } + } + + return glyphy_calc_coverage (xcov, ycov, xwgt, ywgt); +} diff --git a/src/glyphy.h b/src/glyphy.h index 384c626..3a85433 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -174,12 +174,23 @@ glyphy_curve_list_extents (const glyphy_curve_t *curves, #endif typedef struct { - unsigned short r; - unsigned short g; - unsigned short b; - unsigned short a; + short r; + short g; + short b; + short a; } glyphy_texel_t; +/* + * Shader source code + */ + +GLYPHY_API const char * +glyphy_slug_shader_source (void); + +GLYPHY_API const char * +glyphy_slug_shader_source_path (void); + + GLYPHY_API glyphy_bool_t glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int num_curves, diff --git a/src/meson.build b/src/meson.build index 3e70655..54282a7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,14 +1,7 @@ glyphy_sources = [ - 'glyphy-arc.cc', - 'glyphy-arc-bezier.hh', - 'glyphy-arcs.cc', - 'glyphy-arcs-bezier.hh', - 'glyphy-blob.cc', - 'glyphy-common.hh', + 'glyphy-curves.cc', + 'glyphy-encode.cc', 'glyphy-extents.cc', - 'glyphy-geometry.hh', - 'glyphy-outline.cc', - 'glyphy-sdf.cc', 'glyphy-shaders.cc', ] @@ -19,8 +12,7 @@ glyphy_headers = [ ] glyphy_shaders = [ - [ 'glyphy-common.glsl', 'glyphy-common-glsl.h', 'glyphy_common_glsl' ], - [ 'glyphy-sdf.glsl', 'glyphy-sdf-glsl.h', 'glyphy_sdf_glsl' ], + [ 'glyphy-slug.glsl', 'glyphy-slug-glsl.h', 'glyphy_slug_glsl' ], ] stringize = find_program('stringize') From b6fc50da6d951d105312ece39d00a891324a7994 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:38:40 -0600 Subject: [PATCH 04/68] Add demo vertex/fragment shaders and update data structures New vertex shader passes em-space texcoords and flat per-glyph data (band transform, atlas location, band counts) to fragment shader. Fragment shader calls glyphy_slug_render() for coverage. Updated glyph_info_t: replaced nominal_w/h with num_hbands/num_vbands. Updated glyph_vertex_t: new layout with position, texcoord, band transform, and glyph data fields for Slug rendering. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-font.h | 16 ++------ demo/demo-fshader.glsl | 90 ++++++------------------------------------ demo/demo-shader.h | 28 +++++++------ demo/demo-vshader.glsl | 32 ++++++++------- 4 files changed, 48 insertions(+), 118 deletions(-) diff --git a/demo/demo-font.h b/demo/demo-font.h index 0732702..3760070 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifndef DEMO_FONT_H @@ -33,9 +25,9 @@ typedef struct { glyphy_extents_t extents; double advance; - glyphy_bool_t is_empty; /* has no outline; eg. space; don't draw it */ - unsigned int nominal_w; - unsigned int nominal_h; + glyphy_bool_t is_empty; + unsigned int num_hbands; + unsigned int num_vbands; unsigned int atlas_x; unsigned int atlas_y; } glyph_info_t; diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index 9bb884f..8345399 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -1,85 +1,19 @@ -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; +#version 130 -varying vec4 v_glyph; +in vec2 v_texcoord; +flat in vec4 v_bandTransform; +flat in ivec4 v_glyphData; +out vec4 fragColor; -#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() +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 = vec4 (0,0,0,1); - - float gsdist = glyphy_sdf (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); - gsdist -= u_boldness; - float sdist = gsdist / m * u_contrast; - - if (!u_debug) { - 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)); + ivec2 glyphLoc = v_glyphData.xy; + int numHBands = v_glyphData.z; + int numVBands = v_glyphData.w; - 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,.4) * float(arc_list.num_endpoints) * 32./255.; - } + float coverage = glyphy_slug_render (v_texcoord, v_bandTransform, + glyphLoc, numHBands, numVBands); - gl_FragColor = color; + fragColor = vec4 (0.0, 0.0, 0.0, coverage); } diff --git a/demo/demo-shader.h b/demo/demo-shader.h index dfa5480..092cb5b 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifndef DEMO_SHADERS_H @@ -24,12 +16,22 @@ struct glyph_vertex_t { - /* Position */ + /* Object-space position */ GLfloat x; GLfloat y; - /* Glyph info */ - GLfloat g16hi; - GLfloat g16lo; + /* Em-space texture coordinates */ + GLfloat tx; + GLfloat ty; + /* Band transform (constant across glyph) */ + GLfloat band_scale_x; + GLfloat band_scale_y; + GLfloat band_offset_x; + GLfloat band_offset_y; + /* Glyph data (constant across glyph) */ + GLint atlas_x; + GLint atlas_y; + GLint num_hbands; + GLint num_vbands; }; void diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index 90ac4cf..eab35d8 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -1,22 +1,24 @@ +#version 130 + uniform mat4 u_matViewProjection; -attribute vec4 a_glyph_vertex; +/* Per-vertex attributes */ +in vec2 a_position; /* Object-space vertex position */ +in vec2 a_texcoord; /* Em-space sample coordinates */ -varying vec4 v_glyph; +/* Per-vertex but constant across glyph (flat) */ +in vec4 a_bandTransform; /* (scale_x, scale_y, offset_x, offset_y) */ +in ivec4 a_glyphData; /* (atlas_x, atlas_y, num_hbands, num_vbands) */ -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); -} +/* Outputs to fragment shader */ +out vec2 v_texcoord; +flat out vec4 v_bandTransform; +flat out ivec4 v_glyphData; -void -main() +void main () { - gl_Position = u_matViewProjection * vec4 (a_glyph_vertex.xy, 0, 1); - v_glyph = glyph_vertex_transcode (a_glyph_vertex.zw); + gl_Position = u_matViewProjection * vec4 (a_position, 0.0, 1.0); + v_texcoord = a_texcoord; + v_bandTransform = a_bandTransform; + v_glyphData = a_glyphData; } From c1658ef1f6fb30e12840e9a844d0bdaab4c2398a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:47:04 -0600 Subject: [PATCH 05/68] Wire up demo for Slug-style rendering - Atlas: RGBA16I texture with isampler2D, texelFetch upload - Font: use curve accumulator + blob encoder instead of arc pipeline - Shader: assemble slug GLSL + demo fragment shader, new vertex layout with position, texcoord, bandTransform (flat), glyphData (flat) - Buffer: set up 4 vertex attributes including glVertexAttribIPointer for integer glyph data - GLState: simplified, removed old SDF uniforms - HarfBuzz/FreeType headers: updated for curve accumulator, use hb_font_draw_glyph, stub cubic_to for now - Removed demo-atlas.glsl (no longer needed) Builds and links successfully. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-atlas.cc | 44 +++++------- demo/demo-atlas.h | 20 ++---- demo/demo-buffer.cc | 56 +++++++++++----- demo/demo-font.cc | 152 +++++++++++++----------------------------- demo/demo-font.h | 1 + demo/demo-glstate.cc | 58 +++------------- demo/demo-shader.cc | 126 +++++++++++++--------------------- demo/meson.build | 1 - src/glyphy-freetype.h | 44 +++++------- src/glyphy-harfbuzz.h | 35 +++++----- 10 files changed, 195 insertions(+), 342 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index d84da06..79a889f 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifdef HAVE_CONFIG_H @@ -31,7 +23,7 @@ struct demo_atlas_t { GLuint tex_w; GLuint tex_h; GLuint item_w; - GLuint item_h_q; /* height quantum */ + GLuint item_h_q; GLuint cursor_x; GLuint cursor_y; }; @@ -59,10 +51,10 @@ demo_atlas_create (unsigned int w, demo_atlas_bind_texture (at); - glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - gl(TexImage2D) (GL_TEXTURE_2D, 0, GL_RGBA, at->tex_w, at->tex_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + gl(TexImage2D) (GL_TEXTURE_2D, 0, GL_RGBA16I, at->tex_w, at->tex_h, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); return at; } @@ -97,17 +89,15 @@ demo_atlas_set_uniforms (demo_atlas_t *at) GLuint program; glGetIntegerv (GL_CURRENT_PROGRAM, (GLint *) &program); - glUniform4i (glGetUniformLocation (program, "u_atlas_info"), - at->tex_w, at->tex_h, at->item_w, at->item_h_q); - glUniform1i (glGetUniformLocation (program, "u_atlas_tex"), at->tex_unit - GL_TEXTURE0); + glUniform1i (glGetUniformLocation (program, "u_atlas"), at->tex_unit - GL_TEXTURE0); } void -demo_atlas_alloc (demo_atlas_t *at, - glyphy_rgba_t *data, - unsigned int len, - unsigned int *px, - unsigned int *py) +demo_atlas_alloc (demo_atlas_t *at, + glyphy_texel_t *data, + unsigned int len, + unsigned int *px, + unsigned int *py) { GLuint w, h, x, y; @@ -115,7 +105,6 @@ demo_atlas_alloc (demo_atlas_t *at, h = (len + w - 1) / w; if (at->cursor_y + h > at->tex_h) { - /* Go to next column */ at->cursor_x += at->item_w; at->cursor_y = 0; } @@ -131,14 +120,13 @@ demo_atlas_alloc (demo_atlas_t *at, demo_atlas_bind_texture (at); if (w * h == len) - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); + gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA_INTEGER, GL_SHORT, data); else { - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h - 1, GL_RGBA, GL_UNSIGNED_BYTE, data); - /* Upload the last row separately */ - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y + h - 1, len - (w * (h - 1)), 1, GL_RGBA, GL_UNSIGNED_BYTE, + gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h - 1, GL_RGBA_INTEGER, GL_SHORT, data); + gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y + h - 1, len - (w * (h - 1)), 1, GL_RGBA_INTEGER, GL_SHORT, data + w * (h - 1)); } - *px = x / at->item_w; - *py = y / at->item_h_q; + *px = x; + *py = y; } diff --git a/demo/demo-atlas.h b/demo/demo-atlas.h index 729403f..ac5c926 100644 --- a/demo/demo-atlas.h +++ b/demo/demo-atlas.h @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju */ #ifndef DEMO_ATLAS_H @@ -38,11 +30,11 @@ demo_atlas_destroy (demo_atlas_t *at); void -demo_atlas_alloc (demo_atlas_t *at, - glyphy_rgba_t *data, - unsigned int len, - unsigned int *px, - unsigned int *py); +demo_atlas_alloc (demo_atlas_t *at, + glyphy_texel_t *data, + unsigned int len, + unsigned int *px, + unsigned int *py); void demo_atlas_bind_texture (demo_atlas_t *at); diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index dcbb5ff..6275f4a 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifdef HAVE_CONFIG_H @@ -111,7 +103,7 @@ demo_buffer_add_text (demo_buffer_t *buffer, hb_buffer_t *hb_buffer = hb_buffer_create (); glyphy_point_t top_left = buffer->cursor; - buffer->cursor.y += font_size /* * font->ascent */; + buffer->cursor.y += font_size; double scale = font_size / hb_face_get_upem (hb_face); while (utf8) @@ -132,7 +124,6 @@ demo_buffer_add_text (demo_buffer_t *buffer, glyph_info_t gi; demo_font_lookup_glyph (font, glyph_index, &gi); - /* Update ink extents */ glyphy_extents_t ink_extents; glyphy_point_t position = buffer->cursor; position.x += scale * pos[i].x_offset; @@ -140,7 +131,6 @@ demo_buffer_add_text (demo_buffer_t *buffer, demo_shader_add_glyph_vertices (position, font_size, &gi, buffer->vertices, &ink_extents); glyphy_extents_extend (&buffer->ink_extents, &ink_extents); - /* Update logical extents */ glyphy_point_t corner; corner.x = buffer->cursor.x; corner.y = buffer->cursor.y - font_size; @@ -152,7 +142,6 @@ demo_buffer_add_text (demo_buffer_t *buffer, buffer->cursor.x += scale * pos[i].x_advance; } - if (end) { buffer->cursor.y += font_size; buffer->cursor.x = top_left.x; @@ -170,14 +159,45 @@ demo_buffer_draw (demo_buffer_t *buffer) { GLint program; glGetIntegerv (GL_CURRENT_PROGRAM, &program); - GLuint a_glyph_vertex_loc = glGetAttribLocation (program, "a_glyph_vertex"); + glBindBuffer (GL_ARRAY_BUFFER, buffer->buf_name); if (buffer->dirty) { - glBufferData (GL_ARRAY_BUFFER, sizeof (glyph_vertex_t) * buffer->vertices->size (), (const char *) &(*buffer->vertices)[0], GL_STATIC_DRAW); + glBufferData (GL_ARRAY_BUFFER, + sizeof (glyph_vertex_t) * buffer->vertices->size (), + (const char *) &(*buffer->vertices)[0], GL_STATIC_DRAW); buffer->dirty = false; } - glEnableVertexAttribArray (a_glyph_vertex_loc); - glVertexAttribPointer (a_glyph_vertex_loc, 4, GL_FLOAT, GL_FALSE, sizeof (glyph_vertex_t), 0); + + GLsizei stride = sizeof (glyph_vertex_t); + + /* a_position: vec2 at offset 0 */ + GLint loc_pos = glGetAttribLocation (program, "a_position"); + glEnableVertexAttribArray (loc_pos); + glVertexAttribPointer (loc_pos, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, x)); + + /* a_texcoord: vec2 at offset 8 */ + GLint loc_tex = glGetAttribLocation (program, "a_texcoord"); + glEnableVertexAttribArray (loc_tex); + glVertexAttribPointer (loc_tex, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, tx)); + + /* a_bandTransform: vec4 at offset 16 */ + GLint loc_band = glGetAttribLocation (program, "a_bandTransform"); + glEnableVertexAttribArray (loc_band); + glVertexAttribPointer (loc_band, 4, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, band_scale_x)); + + /* a_glyphData: ivec4 at offset 32 */ + GLint loc_glyph = glGetAttribLocation (program, "a_glyphData"); + glEnableVertexAttribArray (loc_glyph); + glVertexAttribIPointer (loc_glyph, 4, GL_INT, stride, + (const void *) offsetof (glyph_vertex_t, atlas_x)); + glDrawArrays (GL_TRIANGLES, 0, buffer->vertices->size ()); - glDisableVertexAttribArray (a_glyph_vertex_loc); + + glDisableVertexAttribArray (loc_pos); + glDisableVertexAttribArray (loc_tex); + glDisableVertexAttribArray (loc_band); + glDisableVertexAttribArray (loc_glyph); } diff --git a/demo/demo-font.cc b/demo/demo-font.cc index a1faeef..1902b81 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifdef HAVE_CONFIG_H @@ -36,13 +28,10 @@ struct demo_font_t { hb_font_t *font; glyph_cache_t *glyph_cache; demo_atlas_t *atlas; - glyphy_arc_accumulator_t *acc; + glyphy_curve_accumulator_t *acc; - /* stats */ unsigned int num_glyphs; - double sum_error; - unsigned int sum_endpoints; - double sum_fetch; + unsigned int sum_curves; unsigned int sum_bytes; }; @@ -57,13 +46,7 @@ demo_font_create (hb_face_t *face, font->font = hb_font_create (face); font->glyph_cache = new glyph_cache_t (); font->atlas = demo_atlas_reference (atlas); - font->acc = glyphy_arc_accumulator_create (); - - font->num_glyphs = 0; - font->sum_error = 0; - font->sum_endpoints = 0; - font->sum_fetch = 0; - font->sum_bytes = 0; + font->acc = glyphy_curve_accumulator_create (); return font; } @@ -81,7 +64,7 @@ demo_font_destroy (demo_font_t *font) if (!font || --font->refcount) return; - glyphy_arc_accumulator_destroy (font->acc); + glyphy_curve_accumulator_destroy (font->acc); demo_atlas_destroy (font->atlas); delete font->glyph_cache; hb_font_destroy (font->font); @@ -110,91 +93,50 @@ demo_font_get_atlas (demo_font_t *font) static glyphy_bool_t -accumulate_endpoint (glyphy_arc_endpoint_t *endpoint, - std::vector *endpoints) +accumulate_curve (const glyphy_curve_t *curve, + std::vector *curves) { - endpoints->push_back (*endpoint); + curves->push_back (*curve); return true; } static void -encode_ft_glyph (demo_font_t *font, - unsigned int glyph_index, - double tolerance_per_em, - glyphy_rgba_t *buffer, - unsigned int buffer_len, - unsigned int *output_len, - unsigned int *nominal_width, - unsigned int *nominal_height, - glyphy_extents_t *extents, - double *advance) +encode_glyph (demo_font_t *font, + unsigned int glyph_index, + glyphy_texel_t *buffer, + unsigned int buffer_len, + unsigned int *output_len, + unsigned int *num_hbands, + unsigned int *num_vbands, + glyphy_extents_t *extents, + double *advance) { -/* Used for testing only */ -#define SCALE (1. * (1 << 0)) + std::vector curves; - unsigned int upem = hb_face_get_upem (font->face); - double tolerance = upem * tolerance_per_em; /* in font design units */ - double faraway = double (upem) / (MIN_FONT_SIZE * M_SQRT2); - double unit_size = double (upem) / GRID_SIZE; - double enlighten_max = double (upem) * ENLIGHTEN_MAX; - double embolden_max = double (upem) * EMBOLDEN_MAX; - std::vector endpoints; - - glyphy_arc_accumulator_reset (font->acc); - glyphy_arc_accumulator_set_tolerance (font->acc, tolerance); - glyphy_arc_accumulator_set_callback (font->acc, - (glyphy_arc_endpoint_accumulator_callback_t) accumulate_endpoint, - &endpoints); + glyphy_curve_accumulator_reset (font->acc); + glyphy_curve_accumulator_set_callback (font->acc, + (glyphy_curve_accumulator_callback_t) accumulate_curve, + &curves); glyphy_harfbuzz(font_get_glyph_shape) (font->font, glyph_index, font->acc); - if (!glyphy_arc_accumulator_successful (font->acc)) - die ("Failed encoding arcs"); - - assert (glyphy_arc_accumulator_get_error (font->acc) <= tolerance); - - if (endpoints.size ()) - glyphy_outline_winding_from_even_odd (&endpoints[0], endpoints.size (), false); - - if (SCALE != 1.) - for (unsigned int i = 0; i < endpoints.size (); i++) - { - endpoints[i].p.x /= SCALE; - endpoints[i].p.y /= SCALE; - } - - double avg_fetch_achieved; - if (!glyphy_arc_list_encode_blob2 (endpoints.size () ? &endpoints[0] : NULL, endpoints.size (), - buffer, - buffer_len, - faraway / SCALE, - unit_size / SCALE, - enlighten_max / SCALE, - embolden_max / SCALE, - &avg_fetch_achieved, - output_len, - nominal_width, - nominal_height, - extents)) - die ("Failed encoding arcs"); + if (!glyphy_curve_accumulator_successful (font->acc)) + die ("Failed accumulating curves"); + if (!glyphy_curve_list_encode_blob (curves.size () ? &curves[0] : NULL, curves.size (), + buffer, buffer_len, + output_len, + num_hbands, num_vbands, + extents)) + die ("Failed encoding blob"); + + unsigned int upem = hb_face_get_upem (font->face); glyphy_extents_scale (extents, 1. / upem, 1. / upem); - glyphy_extents_scale (extents, SCALE, SCALE); *advance = hb_font_get_glyph_h_advance (font->font, glyph_index) / (double) upem; - if (0) - LOGI ("gid%3u: endpoints%3d; err%3g%%; tex fetch%4.1f; mem%4.1fkb\n", - glyph_index, - (unsigned int) glyphy_arc_accumulator_get_num_endpoints (font->acc), - round (100 * glyphy_arc_accumulator_get_error (font->acc) / tolerance), - avg_fetch_achieved, - (*output_len * sizeof (glyphy_rgba_t)) / 1024.); - font->num_glyphs++; - font->sum_error += glyphy_arc_accumulator_get_error (font->acc) / tolerance; - font->sum_endpoints += glyphy_arc_accumulator_get_num_endpoints (font->acc); - font->sum_fetch += avg_fetch_achieved; - font->sum_bytes += (*output_len * sizeof (glyphy_rgba_t)); + font->sum_curves += glyphy_curve_accumulator_get_num_curves (font->acc); + font->sum_bytes += (*output_len * sizeof (glyphy_texel_t)); } static void @@ -202,19 +144,19 @@ _demo_font_upload_glyph (demo_font_t *font, unsigned int glyph_index, glyph_info_t *glyph_info) { - glyphy_rgba_t buffer[4096 * 16]; + glyphy_texel_t buffer[4096]; unsigned int output_len; - encode_ft_glyph (font, - glyph_index, - TOLERANCE, - buffer, ARRAY_LEN (buffer), - &output_len, - &glyph_info->nominal_w, - &glyph_info->nominal_h, - &glyph_info->extents, - &glyph_info->advance); + encode_glyph (font, + glyph_index, + buffer, ARRAY_LEN (buffer), + &output_len, + &glyph_info->num_hbands, + &glyph_info->num_vbands, + &glyph_info->extents, + &glyph_info->advance); + glyph_info->upem = hb_face_get_upem (font->face); glyph_info->is_empty = glyphy_extents_is_empty (&glyph_info->extents); if (!glyph_info->is_empty) demo_atlas_alloc (font->atlas, buffer, output_len, @@ -236,10 +178,8 @@ demo_font_lookup_glyph (demo_font_t *font, void demo_font_print_stats (demo_font_t *font) { - LOGI ("%3d glyphs; avg num endpoints%6.2f; avg error%5.1f%%; avg tex fetch%5.2f; avg %5.2fkb per glyph\n", + LOGI ("%3d glyphs; avg curves%6.2f; avg %5.2fkb per glyph\n", font->num_glyphs, - (double) font->sum_endpoints / font->num_glyphs, - 100. * font->sum_error / font->num_glyphs, - font->sum_fetch / font->num_glyphs, + (double) font->sum_curves / font->num_glyphs, font->sum_bytes / 1024. / font->num_glyphs); } diff --git a/demo/demo-font.h b/demo/demo-font.h index 3760070..3f073c4 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -26,6 +26,7 @@ typedef struct { glyphy_extents_t extents; double advance; glyphy_bool_t is_empty; + unsigned int upem; unsigned int num_hbands; unsigned int num_vbands; unsigned int atlas_x; diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index 459737b..1912868 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski */ #ifdef HAVE_CONFIG_H @@ -27,14 +19,6 @@ struct demo_glstate_t { GLuint program; demo_atlas_t *atlas; - - /* Uniforms */ - double u_debug; - double u_contrast; - double u_gamma_adjust; - double u_outline; - double u_outline_thickness; - double u_boldness; }; demo_glstate_t * @@ -46,14 +30,7 @@ demo_glstate_create (void) st->refcount = 1; st->program = demo_shader_create_program (); - st->atlas = demo_atlas_create (2048, 1024, 64, 8); - - st->u_debug = false; - st->u_contrast = 1.0; - st->u_gamma_adjust = 1.0; - st->u_outline = false; - st->u_outline_thickness = 1.0; - st->u_boldness = 0.; + st->atlas = demo_atlas_create (4096, 4096, 4096, 1); return st; } @@ -77,18 +54,6 @@ demo_glstate_destroy (demo_glstate_t *st) free (st); } - -static void -set_uniform (GLuint program, const char *name, double *p, double value, double scale=1.0) -{ - *p = value; - glUniform1f (glGetUniformLocation (program, name), value * scale); - LOGI ("Setting %s to %g\n", name + 2, value); -} - -#define SET_UNIFORM(name, value) set_uniform (st->program, #name, &st->name, value) -#define SET_UNIFORM_SCALE(name, value, scale) set_uniform (st->program, #name, &st->name, value, scale) - void demo_glstate_setup (demo_glstate_t *st) { @@ -96,13 +61,6 @@ demo_glstate_setup (demo_glstate_t *st) demo_atlas_set_uniforms (st->atlas); - SET_UNIFORM (u_debug, st->u_debug); - SET_UNIFORM (u_contrast, st->u_contrast); - SET_UNIFORM (u_gamma_adjust, st->u_gamma_adjust); - SET_UNIFORM (u_outline, st->u_outline); - SET_UNIFORM (u_outline_thickness, st->u_outline_thickness); - SET_UNIFORM (u_boldness, st->u_boldness); - glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -116,19 +74,19 @@ demo_glstate_get_atlas (demo_glstate_t *st) void demo_glstate_scale_gamma_adjust (demo_glstate_t *st, double factor) { - SET_UNIFORM (u_gamma_adjust, clamp (st->u_gamma_adjust * factor, .1, 10.)); + (void) st; (void) factor; } void demo_glstate_scale_contrast (demo_glstate_t *st, double factor) { - SET_UNIFORM (u_contrast, clamp (st->u_contrast * factor, .1, 10.)); + (void) st; (void) factor; } void demo_glstate_toggle_debug (demo_glstate_t *st) { - SET_UNIFORM (u_debug, 1 - st->u_debug); + (void) st; } void @@ -140,17 +98,17 @@ demo_glstate_set_matrix (demo_glstate_t *st, float mat[16]) void demo_glstate_toggle_outline (demo_glstate_t *st) { - SET_UNIFORM (u_outline, 1 - st->u_outline); + (void) st; } void demo_glstate_scale_outline_thickness (demo_glstate_t *st, double factor) { - SET_UNIFORM (u_outline_thickness, clamp (st->u_outline_thickness * factor, .5, 3.)); + (void) st; (void) factor; } void demo_glstate_adjust_boldness (demo_glstate_t *st, double adjustment) { - SET_UNIFORM_SCALE (u_boldness, clamp (st->u_boldness + adjustment, -ENLIGHTEN_MAX, EMBOLDEN_MAX), GRID_SIZE); + (void) st; (void) adjustment; } diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 9b82123..d964158 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifdef HAVE_CONFIG_H @@ -22,47 +14,10 @@ #include "demo-shader.h" -#include "demo-atlas-glsl.h" #include "demo-vshader-glsl.h" #include "demo-fshader-glsl.h" -static unsigned int -glyph_encode (unsigned int atlas_x , /* 7 bits */ - unsigned int atlas_y, /* 7 bits */ - unsigned int corner_x, /* 1 bit */ - unsigned int corner_y, /* 1 bit */ - unsigned int nominal_w, /* 6 bits */ - unsigned int nominal_h /* 6 bits */) -{ - assert (0 == (atlas_x & ~0x7F)); - assert (0 == (atlas_y & ~0x7F)); - assert (0 == (corner_x & ~1)); - assert (0 == (corner_y & ~1)); - assert (0 == (nominal_w & ~0x3F)); - assert (0 == (nominal_h & ~0x3F)); - - unsigned int x = (((atlas_x << 6) | nominal_w) << 1) | corner_x; - unsigned int y = (((atlas_y << 6) | nominal_h) << 1) | corner_y; - - return (x << 16) | y; -} - -static void -glyph_vertex_encode (double x, double y, - unsigned int corner_x, unsigned int corner_y, - const glyph_info_t *gi, - glyph_vertex_t *v) -{ - unsigned int encoded = glyph_encode (gi->atlas_x, gi->atlas_y, - corner_x, corner_y, - gi->nominal_w, gi->nominal_h); - v->x = x; - v->y = y; - v->g16hi = encoded >> 16; - v->g16lo = encoded & 0xFFFF; -} - void demo_shader_add_glyph_vertices (const glyphy_point_t &p, double font_size, @@ -73,20 +28,50 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, if (gi->is_empty) return; + /* gi->extents are in normalized em-space (0..1). + * The blob stores curves in font design units. + * We need texcoords in font design units to match, + * so multiply back by upem. Band transform also in font units. */ + double upem = gi->upem; + double min_x = gi->extents.min_x * upem; + double max_x = gi->extents.max_x * upem; + double min_y = gi->extents.min_y * upem; + double max_y = gi->extents.max_y * upem; + double width = max_x - min_x; + double height = max_y - min_y; + + float band_scale_x = (width > 0) ? (float) (gi->num_vbands / width) : 0.f; + float band_scale_y = (height > 0) ? (float) (gi->num_hbands / height) : 0.f; + float band_offset_x = (float) (-min_x * band_scale_x); + float band_offset_y = (float) (-min_y * band_scale_y); + glyph_vertex_t v[4]; -#define ENCODE_CORNER(_cx, _cy) \ - do { \ - double _vx = p.x + font_size * ((1-_cx) * gi->extents.min_x + _cx * gi->extents.max_x); \ - double _vy = p.y - font_size * ((1-_cy) * gi->extents.min_y + _cy * gi->extents.max_y); \ - glyph_vertex_encode (_vx, _vy, _cx, _cy, gi, &v[_cx * 2 + _cy]); \ - } while (0) - ENCODE_CORNER (0, 0); - ENCODE_CORNER (0, 1); - ENCODE_CORNER (1, 0); - ENCODE_CORNER (1, 1); -#undef ENCODE_CORNER + for (int ci = 0; ci < 4; ci++) { + int cx = (ci >> 1) & 1; + int cy = ci & 1; + + double vx = p.x + font_size * ((1 - cx) * gi->extents.min_x + cx * gi->extents.max_x); + double vy = p.y - font_size * ((1 - cy) * gi->extents.min_y + cy * gi->extents.max_y); + + double tx = (1 - cx) * min_x + cx * max_x; + double ty = (1 - cy) * min_y + cy * max_y; + + v[ci].x = (float) vx; + v[ci].y = (float) vy; + v[ci].tx = (float) tx; + v[ci].ty = (float) ty; + v[ci].band_scale_x = band_scale_x; + v[ci].band_scale_y = band_scale_y; + v[ci].band_offset_x = band_offset_x; + v[ci].band_offset_y = band_offset_y; + v[ci].atlas_x = gi->atlas_x; + v[ci].atlas_y = gi->atlas_y; + v[ci].num_hbands = gi->num_hbands; + v[ci].num_vbands = gi->num_vbands; + } + /* Two triangles */ vertices->push_back (v[0]); vertices->push_back (v[1]); vertices->push_back (v[2]); @@ -97,16 +82,14 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, if (extents) { glyphy_extents_clear (extents); - for (unsigned int i = 0; i < 4; i++) { - glyphy_point_t p = {v[i].x, v[i].y}; - glyphy_extents_add (extents, &p); + for (int i = 0; i < 4; i++) { + glyphy_point_t pt = {v[i].x, v[i].y}; + glyphy_extents_add (extents, &pt); } } } - - static GLuint compile_shader (GLenum type, GLsizei count, @@ -180,30 +163,15 @@ link_program (GLuint vshader, return program; } -#ifdef GL_ES_VERSION_2_0 -# define GLSL_HEADER_STRING \ - "#extension GL_OES_standard_derivatives : enable\n" \ - "precision highp float;\n" \ - "precision highp int;\n" -#else -# define GLSL_HEADER_STRING \ - "#version 110\n" -#endif - GLuint demo_shader_create_program (void) { TRACE(); GLuint vshader, fshader, program; - const GLchar *vshader_sources[] = {GLSL_HEADER_STRING, - demo_vshader_glsl}; + const GLchar *vshader_sources[] = {demo_vshader_glsl}; vshader = compile_shader (GL_VERTEX_SHADER, ARRAY_LEN (vshader_sources), vshader_sources); - const GLchar *fshader_sources[] = {GLSL_HEADER_STRING, - demo_atlas_glsl, - glyphy_common_shader_source (), - "#define GLYPHY_SDF_PSEUDO_DISTANCE 1\n", - glyphy_sdf_shader_source (), + const GLchar *fshader_sources[] = {glyphy_slug_shader_source (), demo_fshader_glsl}; fshader = compile_shader (GL_FRAGMENT_SHADER, ARRAY_LEN (fshader_sources), fshader_sources); diff --git a/demo/meson.build b/demo/meson.build index 47f0d0b..d2c8474 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -20,7 +20,6 @@ demo_sources = [ ] demo_shaders = [ - [ 'demo-atlas.glsl', 'demo-atlas-glsl.h', 'demo_atlas_glsl' ], [ 'demo-fshader.glsl', 'demo-fshader-glsl.h', 'demo_fshader_glsl' ], [ 'demo-vshader.glsl', 'demo-vshader-glsl.h', 'demo_vshader_glsl' ], [ 'default-text.txt', 'default-text.h', 'default_text' ], diff --git a/src/glyphy-freetype.h b/src/glyphy-freetype.h index 94474ac..df6952f 100644 --- a/src/glyphy-freetype.h +++ b/src/glyphy-freetype.h @@ -6,14 +6,6 @@ * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju */ /* Intentionally doesn't have include guards */ @@ -43,47 +35,47 @@ extern "C" { static int glyphy_freetype(move_to) (FT_Vector *to, - glyphy_arc_accumulator_t *acc) + glyphy_curve_accumulator_t *acc) { glyphy_point_t p1 = {(double) to->x, (double) to->y}; - glyphy_arc_accumulator_close_path (acc); - glyphy_arc_accumulator_move_to (acc, &p1); - return glyphy_arc_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; + glyphy_curve_accumulator_close_path (acc); + glyphy_curve_accumulator_move_to (acc, &p1); + return glyphy_curve_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; } static int glyphy_freetype(line_to) (FT_Vector *to, - glyphy_arc_accumulator_t *acc) + glyphy_curve_accumulator_t *acc) { glyphy_point_t p1 = {(double) to->x, (double) to->y}; - glyphy_arc_accumulator_line_to (acc, &p1); - return glyphy_arc_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; + glyphy_curve_accumulator_line_to (acc, &p1); + return glyphy_curve_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; } static int glyphy_freetype(conic_to) (FT_Vector *control, FT_Vector *to, - glyphy_arc_accumulator_t *acc) + glyphy_curve_accumulator_t *acc) { glyphy_point_t p1 = {(double) control->x, (double) control->y}; glyphy_point_t p2 = {(double) to->x, (double) to->y}; - glyphy_arc_accumulator_conic_to (acc, &p1, &p2); - return glyphy_arc_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; + glyphy_curve_accumulator_conic_to (acc, &p1, &p2); + return glyphy_curve_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; } static int glyphy_freetype(cubic_to) (FT_Vector *control1, FT_Vector *control2, FT_Vector *to, - glyphy_arc_accumulator_t *acc) + glyphy_curve_accumulator_t *acc) { - glyphy_point_t p1 = {(double) control1->x, (double) control1->y}; - glyphy_point_t p2 = {(double) control2->x, (double) control2->y}; - glyphy_point_t p3 = {(double) to->x, (double) to->y}; - glyphy_arc_accumulator_cubic_to (acc, &p1, &p2, &p3); - return glyphy_arc_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; + /* TODO: cubics not supported yet; need cu2qu converter */ + (void) control1; (void) control2; + glyphy_point_t p1 = {(double) to->x, (double) to->y}; + glyphy_curve_accumulator_line_to (acc, &p1); + return glyphy_curve_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; } static FT_Error -glyphy_freetype(outline_decompose) (const FT_Outline *outline, - glyphy_arc_accumulator_t *acc) +glyphy_freetype(outline_decompose) (const FT_Outline *outline, + glyphy_curve_accumulator_t *acc) { const FT_Outline_Funcs outline_funcs = { (FT_Outline_MoveToFunc) glyphy_freetype(move_to), diff --git a/src/glyphy-harfbuzz.h b/src/glyphy-harfbuzz.h index b2a8cfc..2b1cf5e 100644 --- a/src/glyphy-harfbuzz.h +++ b/src/glyphy-harfbuzz.h @@ -6,12 +6,6 @@ * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ /* Intentionally doesn't have include guards */ @@ -39,30 +33,30 @@ extern "C" { static void glyphy_harfbuzz(move_to) (hb_draw_funcs_t *dfuncs, - glyphy_arc_accumulator_t *acc, + glyphy_curve_accumulator_t *acc, hb_draw_state_t *st, float to_x, float to_y, void *user_data) { glyphy_point_t p1 = {(double) to_x, (double) to_y}; - glyphy_arc_accumulator_close_path (acc); - glyphy_arc_accumulator_move_to (acc, &p1); + glyphy_curve_accumulator_close_path (acc); + glyphy_curve_accumulator_move_to (acc, &p1); } static void glyphy_harfbuzz(line_to) (hb_draw_funcs_t *dfuncs, - glyphy_arc_accumulator_t *acc, + glyphy_curve_accumulator_t *acc, hb_draw_state_t *st, float to_x, float to_y, void *user_data) { glyphy_point_t p1 = {(double) to_x, (double) to_y}; - glyphy_arc_accumulator_line_to (acc, &p1); + glyphy_curve_accumulator_line_to (acc, &p1); } static void glyphy_harfbuzz(quadratic_to) (hb_draw_funcs_t *dfuncs, - glyphy_arc_accumulator_t *acc, + glyphy_curve_accumulator_t *acc, hb_draw_state_t *st, float control_x, float control_y, float to_x, float to_y, @@ -70,22 +64,23 @@ glyphy_harfbuzz(quadratic_to) (hb_draw_funcs_t *dfuncs, { glyphy_point_t p1 = {(double) control_x, (double) control_y}; glyphy_point_t p2 = {(double) to_x, (double) to_y}; - glyphy_arc_accumulator_conic_to (acc, &p1, &p2); + glyphy_curve_accumulator_conic_to (acc, &p1, &p2); } static void glyphy_harfbuzz(cubic_to) (hb_draw_funcs_t *dfuncs, - glyphy_arc_accumulator_t *acc, + glyphy_curve_accumulator_t *acc, hb_draw_state_t *st, float control1_x, float control1_y, float control2_x, float control2_y, float to_x, float to_y, void *user_data) { - glyphy_point_t p1 = {(double) control1_x, (double) control1_y}; - glyphy_point_t p2 = {(double) control2_x, (double) control2_y}; - glyphy_point_t p3 = {(double) to_x, (double) to_y}; - glyphy_arc_accumulator_cubic_to (acc, &p1, &p2, &p3); + /* TODO: cubics not supported yet; need cu2qu converter */ + (void) control1_x; (void) control1_y; + (void) control2_x; (void) control2_y; + glyphy_point_t p1 = {(double) to_x, (double) to_y}; + glyphy_curve_accumulator_line_to (acc, &p1); } static hb_draw_funcs_t * @@ -109,9 +104,9 @@ glyphy_harfbuzz(get_draw_funcs) (void) static void glyphy_harfbuzz(font_get_glyph_shape) (hb_font_t *font, hb_codepoint_t glyph, - glyphy_arc_accumulator_t *acc) + glyphy_curve_accumulator_t *acc) { - hb_font_get_glyph_shape (font, glyph, glyphy_harfbuzz(get_draw_funcs) (), acc); + hb_font_draw_glyph (font, glyph, glyphy_harfbuzz(get_draw_funcs) (), acc); } #ifdef __cplusplus From 1a7695cb49c0f7ac25936595b95c74c4897e5909 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:48:18 -0600 Subject: [PATCH 06/68] Fix demo buffer size for complex glyphs Use 16384 texels (vs 4096) to handle complex glyphs with many curves. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-font.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 1902b81..37e495e 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -144,7 +144,7 @@ _demo_font_upload_glyph (demo_font_t *font, unsigned int glyph_index, glyph_info_t *glyph_info) { - glyphy_texel_t buffer[4096]; + glyphy_texel_t buffer[16384]; unsigned int output_len; encode_glyph (font, From 6080bdef5abf817c95687cffd51f6528d5f3da07 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:56:44 -0600 Subject: [PATCH 07/68] Fix shader compilation: require GLSL 3.30, remove stale headers - Bump to #version 330 (needed for floatBitsToUint) - Remove #version from demo-fshader.glsl (prepended by demo-shader.cc) - Remove demo-atlas.glsl from Makefile.am (no longer used) - Update src/Makefile.am for new source files and shader - Slug shader by Eric Lengyel, ported to GLSL by Behdad Esfahbod Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/Makefile.am | 1 - demo/demo-fshader.glsl | 2 -- demo/demo-shader.cc | 3 ++- demo/demo-vshader.glsl | 2 +- src/Makefile.am | 14 +++----------- src/glyphy-slug.glsl | 2 +- 6 files changed, 7 insertions(+), 17 deletions(-) diff --git a/demo/Makefile.am b/demo/Makefile.am index c611be3..c6f79f1 100644 --- a/demo/Makefile.am +++ b/demo/Makefile.am @@ -66,7 +66,6 @@ glyphy_demo_SOURCES = \ $(NULL) endif SHADERS = \ - demo-atlas.glsl \ demo-fshader.glsl \ demo-vshader.glsl \ $(NULL) diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index 8345399..944f27e 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -1,5 +1,3 @@ -#version 130 - in vec2 v_texcoord; flat in vec4 v_bandTransform; flat in ivec4 v_glyphData; diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index d964158..b80c797 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -171,7 +171,8 @@ demo_shader_create_program (void) GLuint vshader, fshader, program; const GLchar *vshader_sources[] = {demo_vshader_glsl}; vshader = compile_shader (GL_VERTEX_SHADER, ARRAY_LEN (vshader_sources), vshader_sources); - const GLchar *fshader_sources[] = {glyphy_slug_shader_source (), + const GLchar *fshader_sources[] = {"#version 330\n", + glyphy_slug_shader_source (), demo_fshader_glsl}; fshader = compile_shader (GL_FRAGMENT_SHADER, ARRAY_LEN (fshader_sources), fshader_sources); diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index eab35d8..aee24ba 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -1,4 +1,4 @@ -#version 130 +#version 330 uniform mat4 u_matViewProjection; diff --git a/src/Makefile.am b/src/Makefile.am index 4b6bdb8..d98acb7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,16 +15,9 @@ libglyphy_la_LDFLAGS = \ -version-info @GLYPHY_LIBTOOL_VERSION_INFO@ \ $(NULL) libglyphy_la_SOURCES = \ - glyphy-arc.cc \ - glyphy-arc-bezier.hh \ - glyphy-arcs.cc \ - glyphy-arcs-bezier.hh \ - glyphy-blob.cc \ - glyphy-common.hh \ + glyphy-curves.cc \ + glyphy-encode.cc \ glyphy-extents.cc \ - glyphy-geometry.hh \ - glyphy-outline.cc \ - glyphy-sdf.cc \ glyphy-shaders.cc \ $(PUBLICHEADERS) \ $(SHADERHEADERS) \ @@ -35,8 +28,7 @@ PUBLICHEADERS = \ glyphy-harfbuzz.h \ $(NULL) SHADERS = \ - glyphy-common.glsl \ - glyphy-sdf.glsl \ + glyphy-slug.glsl \ $(NULL) SHADERHEADERS = $(patsubst %.glsl,%-glsl.h, $(SHADERS)) BUILT_SOURCES += $(SHADERHEADERS) diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index 4af923c..998c17a 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -10,7 +10,7 @@ */ -/* Requires GLSL 1.30 / ES 3.00 */ +/* Requires GLSL 3.30 / ES 3.00 */ #ifndef GLYPHY_ATLAS_WIDTH From 766b3ac5ac5d4847388a49bd1f4d76b2854688de Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 21:59:54 -0600 Subject: [PATCH 08/68] Remove old arc/SDF pipeline and unused files Delete all arc geometry, bezier-to-arc approximation, SDF calculation, grid-based blob encoding, outline manipulation, and glyphy-validate. Fix glyphy-extents.cc to not depend on deleted glyphy-common.hh. Update both autotools and meson build files. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/Makefile.am | 16 - demo/demo-atlas-glsl.h | 18 + demo/demo-atlas.glsl | 16 - demo/glyphy-validate.cc | 153 -------- src/glyphy-arc-bezier.hh | 228 ------------ src/glyphy-arc.cc | 137 ------- src/glyphy-arcs-bezier.hh | 153 -------- src/glyphy-arcs.cc | 320 ---------------- src/glyphy-blob.cc | 367 ------------------- src/glyphy-common-glsl.h | 224 ------------ src/glyphy-common.glsl | 222 ------------ src/glyphy-common.hh | 60 --- src/glyphy-extents.cc | 15 +- src/glyphy-geometry.hh | 742 -------------------------------------- src/glyphy-outline.cc | 327 ----------------- src/glyphy-sdf-glsl.h | 155 -------- src/glyphy-sdf.cc | 91 ----- src/glyphy-sdf.glsl | 153 -------- 18 files changed, 27 insertions(+), 3370 deletions(-) create mode 100644 demo/demo-atlas-glsl.h delete mode 100644 demo/demo-atlas.glsl delete mode 100644 demo/glyphy-validate.cc delete mode 100644 src/glyphy-arc-bezier.hh delete mode 100644 src/glyphy-arc.cc delete mode 100644 src/glyphy-arcs-bezier.hh delete mode 100644 src/glyphy-arcs.cc delete mode 100644 src/glyphy-blob.cc delete mode 100644 src/glyphy-common-glsl.h delete mode 100644 src/glyphy-common.glsl delete mode 100644 src/glyphy-common.hh delete mode 100644 src/glyphy-geometry.hh delete mode 100644 src/glyphy-outline.cc delete mode 100644 src/glyphy-sdf-glsl.h delete mode 100644 src/glyphy-sdf.cc delete mode 100644 src/glyphy-sdf.glsl diff --git a/demo/Makefile.am b/demo/Makefile.am index c6f79f1..5ae517c 100644 --- a/demo/Makefile.am +++ b/demo/Makefile.am @@ -83,22 +83,6 @@ endif endif -if HAVE_FREETYPE2 - -noinst_PROGRAMS += glyphy-validate -glyphy_validate_CPPFLAGS = \ - -I $(top_srcdir)/src \ - $(FREETYPE2_CFLAGS) \ - $(NULL) -glyphy_validate_LDADD = \ - $(top_builddir)/src/libglyphy.la \ - $(FREETYPE2_LIBS) \ - $(NULL) -glyphy_validate_SOURCES = \ - glyphy-validate.cc \ - $(NULL) - -endif -include $(top_srcdir)/git.mk diff --git a/demo/demo-atlas-glsl.h b/demo/demo-atlas-glsl.h new file mode 100644 index 0000000..9b93c2a --- /dev/null +++ b/demo/demo-atlas-glsl.h @@ -0,0 +1,18 @@ +static const char *demo_atlas_glsl = +"uniform sampler2D u_atlas_tex;\n" +"uniform ivec4 u_atlas_info;\n" +"\n" +"#define GLYPHY_TEXTURE1D_EXTRA_DECLS , sampler2D _tex, ivec4 _atlas_info, ivec2 _atlas_pos\n" +"#define GLYPHY_TEXTURE1D_EXTRA_ARGS , _tex, _atlas_info, _atlas_pos\n" +"#define GLYPHY_DEMO_EXTRA_ARGS , u_atlas_tex, u_atlas_info, gi.atlas_pos\n" +"\n" +"vec4\n" +"glyphy_texture1D_func (int offset GLYPHY_TEXTURE1D_EXTRA_DECLS)\n" +"{\n" +" ivec2 item_geom = _atlas_info.zw;\n" +" vec2 pos = (vec2 (_atlas_pos.xy * item_geom +\n" +" ivec2 (mod (float (offset), float (item_geom.x)), offset / item_geom.x)) +\n" +" + vec2 (.5, .5)) / vec2(_atlas_info.xy);\n" +" return texture2D (_tex, pos);\n" +"}\n" +; diff --git a/demo/demo-atlas.glsl b/demo/demo-atlas.glsl deleted file mode 100644 index ec6bc1a..0000000 --- a/demo/demo-atlas.glsl +++ /dev/null @@ -1,16 +0,0 @@ -uniform sampler2D u_atlas_tex; -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_atlas_tex, 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 texture2D (_tex, pos); -} diff --git a/demo/glyphy-validate.cc b/demo/glyphy-validate.cc deleted file mode 100644 index b89bed6..0000000 --- a/demo/glyphy-validate.cc +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include - -#define TOLERANCE (1./2048) - -#include - -#include - -using namespace std; - -static inline void -die (const char *msg) -{ - fprintf (stderr, "%s\n", msg); - exit (1); -} - -static glyphy_bool_t -accumulate_endpoint (glyphy_arc_endpoint_t *endpoint, - vector *endpoints) -{ - endpoints->push_back (*endpoint); - return true; -} - -int -main (int argc, char** argv) -{ - bool verbose = false; - - if (argc > 1 && 0 == strcmp (argv[1], "--verbose")) { - verbose = true; - argc--; - argv++; - } - - if (argc == 1) { - fprintf (stderr, "Usage: %s FONT_FILE...\n", argv[0]); - exit (1); - } - - FT_Library ft_library; - FT_Init_FreeType (&ft_library); - - glyphy_arc_accumulator_t *acc = glyphy_arc_accumulator_create (); - - for (unsigned int arg = 1; (int) arg < argc; arg++) - { - const char *font_path = argv[arg]; - - unsigned int num_faces = 1; - for (unsigned int face_index = 0; face_index < num_faces; face_index++) - { - FT_Face ft_face = NULL; - FT_New_Face (ft_library, font_path, face_index, &ft_face); - if (!ft_face) - die ("Failed to open font file"); - /* FreeType's absurd. You have to open a ft_face to get the number of - * faces in the font file. */ - num_faces = ft_face->num_faces; - printf ("Opened %s face index %d. Has %d glyphs\n", - font_path, face_index, (int) ft_face->num_glyphs); - - for (unsigned int glyph_index = 0; glyph_index < ft_face->num_glyphs; glyph_index++) - { - char glyph_name[30]; - if (FT_Get_Glyph_Name (ft_face, glyph_index, glyph_name, sizeof (glyph_name))) - sprintf (glyph_name, "gid%u", glyph_index); - - printf ("Processing glyph %d (%s)\n", glyph_index, glyph_name); - - if (FT_Err_Ok != FT_Load_Glyph (ft_face, - glyph_index, - FT_LOAD_NO_BITMAP | - FT_LOAD_NO_HINTING | - FT_LOAD_NO_AUTOHINT | - FT_LOAD_NO_SCALE | - FT_LOAD_LINEAR_DESIGN | - FT_LOAD_IGNORE_TRANSFORM)) - die ("Failed loading FreeType glyph"); - - if (ft_face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) - die ("FreeType loaded glyph format is not outline"); - - unsigned int upem = ft_face->units_per_EM; - double tolerance = upem * TOLERANCE; /* in font design units */ - vector endpoints; - - glyphy_arc_accumulator_reset (acc); - glyphy_arc_accumulator_set_tolerance (acc, tolerance); - glyphy_arc_accumulator_set_callback (acc, - (glyphy_arc_endpoint_accumulator_callback_t) accumulate_endpoint, - &endpoints); - - if (FT_Err_Ok != glyphy_freetype(outline_decompose) (&ft_face->glyph->outline, acc)) - die ("Failed converting glyph outline to arcs"); - - if (verbose) { - printf ("Arc list has %d endpoints\n", (int) endpoints.size ()); - for (unsigned int i = 0; i < endpoints.size (); i++) - printf ("Endpoint %d: p=(%g,%g),d=%g\n", i, endpoints[i].p.x, endpoints[i].p.y, endpoints[i].d); - } - - assert (glyphy_arc_accumulator_get_error (acc) <= tolerance); - -#if 0 - if (ft_face->glyph->outline.flags & FT_OUTLINE_EVEN_ODD_FILL) - glyphy_outline_winding_from_even_odd (&endpoints[0], endpoints.size (), false); -#endif - if (ft_face->glyph->outline.flags & FT_OUTLINE_REVERSE_FILL) - glyphy_outline_reverse (&endpoints[0], endpoints.size ()); - - if (glyphy_outline_winding_from_even_odd (&endpoints[0], endpoints.size (), false)) - { - fprintf (stderr, "ERROR: %s:%d: Glyph %d (%s) has contours with wrong direction\n", - font_path, face_index, glyph_index, glyph_name); - } - } - - FT_Done_Face (ft_face); - } - } - - glyphy_arc_accumulator_destroy (acc); - - FT_Done_FreeType (ft_library); - - return 0; -} diff --git a/src/glyphy-arc-bezier.hh b/src/glyphy-arc-bezier.hh deleted file mode 100644 index ec14660..0000000 --- a/src/glyphy-arc-bezier.hh +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2012,2013 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - -#ifndef GLYPHY_ARC_BEZIER_HH -#define GLYPHY_ARC_BEZIER_HH - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" - -namespace GLyphy { -namespace ArcBezier { - -using namespace Geometry; - - -class MaxDeviationApproximatorExact -{ - public: - /* Returns 3 max(abs(d₀ t (1-t)² + d₁ t² (1-t)) for 0≤t≤1. */ - static double approximate_deviation (double d0, double d1) - { - double candidates[4] = {0,1}; - unsigned int num_candidates = 2; - if (d0 == d1) - candidates[num_candidates++] = .5; - else { - double delta = d0*d0 - d0*d1 + d1*d1; - double t2 = 1. / (3 * (d0 - d1)); - double t0 = (2 * d0 - d1) * t2; - if (delta == 0) - candidates[num_candidates++] = t0; - else if (delta > 0) { - /* This code can be optimized to avoid the sqrt if the solution - * is not feasible (ie. lies outside (0,1)). I have implemented - * that in cairo-spline.c:_cairo_spline_bound(). Can be reused - * here. - */ - double t1 = sqrt (delta) * t2; - candidates[num_candidates++] = t0 - t1; - candidates[num_candidates++] = t0 + t1; - } - } - - double e = 0; - for (unsigned int i = 0; i < num_candidates; i++) { - double t = candidates[i]; - double ee; - if (t < 0. || t > 1.) - continue; - ee = fabs (3 * t * (1-t) * (d0 * (1 - t) + d1 * t)); - e = std::max (e, ee); - } - - return e; - } -}; - - - -template -class ArcBezierErrorApproximatorBehdad -{ - public: - static double approximate_bezier_arc_error (const Bezier &b0, const Arc &a) - { - assert (b0.p0 == a.p0); - assert (b0.p3 == a.p1); - - double ea; - Bezier b1 = a.approximate_bezier (&ea); - - assert (b0.p0 == b1.p0); - assert (b0.p3 == b1.p3); - - Vector v0 = b1.p1 - b0.p1; - Vector v1 = b1.p2 - b0.p2; - - Vector b = (b0.p3 - b0.p0).normalized (); - v0 = v0.rebase (b); - v1 = v1.rebase (b); - - Vector v (MaxDeviationApproximator::approximate_deviation (v0.dx, v1.dx), - MaxDeviationApproximator::approximate_deviation (v0.dy, v1.dy)); - - /* Edge cases: If d*d is too close too large default to a weak bound. */ - if (a.d * a.d > 1. - 1e-4) - return ea + v.len (); - - /* If the wedge doesn't contain control points, default to weak bound. */ - if (!a.wedge_contains_point (b0.p1) || !a.wedge_contains_point (b0.p2)) - return ea + v.len (); - - /* If straight line, return the max ortho deviation. */ - if (fabs (a.d) < 1e-6) - return ea + v.dy; - - /* We made sure that fabs(a.d) < 1 */ - double tan_half_alpha = fabs (tan2atan (a.d)); - - double tan_v = v.dx / v.dy; - - double eb; - if (fabs (tan_v) <= tan_half_alpha) - return ea + v.len (); - - double c2 = (a.p1 - a.p0).len () * .5; - double r = a.radius (); - - eb = Vector (c2 + v.dx, c2 / tan_half_alpha + v.dy).len () - r; - assert (eb >= 0); - - return ea + eb; - } -}; - - - -template -class ArcBezierApproximatorMidpointSimple -{ - public: - static const Arc approximate_bezier_with_arc (const Bezier &b, double *error) - { - Arc a (b.p0, b.p3, b.midpoint (), false); - - *error = ArcBezierErrorApproximator::approximate_bezier_arc_error (b, a); - - return a; - } -}; - -template -class ArcBezierApproximatorMidpointTwoPart -{ - public: - static const Arc approximate_bezier_with_arc (const Bezier &b, double *error, double mid_t = .5) - { - Pair pair = b.split (mid_t); - Point m = pair.second.p0; - - Arc a0 (b.p0, m, b.p3, true); - Arc a1 (m, b.p3, b.p0, true); - - double e0 = ArcBezierErrorApproximator::approximate_bezier_arc_error (pair.first, a0); - double e1 = ArcBezierErrorApproximator::approximate_bezier_arc_error (pair.second, a1); - *error = std::max (e0, e1); - - return Arc (b.p0, b.p3, m, false); - } -}; - -template -class ArcBezierApproximatorQuantized -{ - public: - ArcBezierApproximatorQuantized (double _max_d = GLYPHY_INFINITY, unsigned int _d_bits = 0) : - max_d (_max_d), d_bits (_d_bits) {}; - - protected: - double max_d; - unsigned int d_bits; - - public: - const Arc approximate_bezier_with_arc (const Bezier &b, double *error) const - { - double mid_t = .5; - Arc a (b.p0, b.p3, b.point (mid_t), false); - Arc orig_a = a; - - if (isfinite (max_d)) { - assert (max_d >= 0); - if (fabs (a.d) > max_d) - a.d = a.d < 0 ? -max_d : max_d; - } - if (d_bits && max_d != 0) { - assert (isfinite (max_d)); - assert (fabs (a.d) <= max_d); - int mult = (1 << (d_bits - 1)) - 1; - int id = round (a.d / max_d * mult); - assert (-mult <= id && id <= mult); - a.d = id * max_d / mult; - assert (fabs (a.d) <= max_d); - } - - /* Error introduced by arc quantization */ - double ed = fabs (a.d - orig_a.d) * (a.p1 - a.p0).len () * .5; - - ArcBezierApproximatorMidpointTwoPart - ::approximate_bezier_with_arc (b, error, mid_t); - - if (ed) { - *error += ed; - - /* Try a simple one-arc approx which works with the quantized arc. - * May produce smaller error bound. */ - double e = ArcBezierErrorApproximator::approximate_bezier_arc_error (b, a); - if (e < *error) - *error = e; - } - - return a; - } -}; - -typedef MaxDeviationApproximatorExact MaxDeviationApproximatorDefault; -typedef ArcBezierErrorApproximatorBehdad ArcBezierErrorApproximatorDefault; -typedef ArcBezierApproximatorMidpointTwoPart ArcBezierApproximatorDefault; -typedef ArcBezierApproximatorQuantized ArcBezierApproximatorQuantizedDefault; - -} /* namespace ArcBezier */ -} /* namespace GLyphy */ - -#endif /* GLYPHY_ARC_BEZIER_HH */ diff --git a/src/glyphy-arc.cc b/src/glyphy-arc.cc deleted file mode 100644 index 650e231..0000000 --- a/src/glyphy-arc.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" -#include "glyphy-arc-bezier.hh" - -using namespace GLyphy::Geometry; -using namespace GLyphy::ArcBezier; - - - -/* - * Circular arcs - */ - - -/* Build from a conventional arc representation */ -void -glyphy_arc_from_conventional (const glyphy_point_t *center, - double radius, - double angle0, - double angle1, - glyphy_bool_t negative, - glyphy_arc_t *arc) -{ - *arc = Arc (*center, radius, angle0, angle1, negative); -}; - -/* Convert to a conventional arc representation */ -void -glyphy_arc_to_conventional (glyphy_arc_t arc, - glyphy_point_t *center /* may be NULL */, - double *radius /* may be NULL */, - double *angle0 /* may be NULL */, - double *angle1 /* may be NULL */, - glyphy_bool_t *negative /* may be NULL */) -{ - Arc a (arc); - if (radius) *radius = a.radius (); - if (center || angle0 || angle1) { - Point c = a.center (); - if (center) *center = c; - if (angle0) *angle0 = (a.p0 - c).angle (); - if (angle1) *angle1 = (a.p1 - c).angle (); - if (negative) *negative = a.d < 0; - } -} - -glyphy_bool_t -glyphy_arc_is_a_line (glyphy_arc_t arc) -{ - return arc.d == 0; -} - -void -glyphy_arc_extents (glyphy_arc_t arc, - glyphy_extents_t *extents) -{ - Arc(arc).extents (*extents); -} - - -/* - * Approximate single pieces of geometry to/from one arc - */ - - -void -glyphy_arc_from_line (const glyphy_point_t *p0, - const glyphy_point_t *p1, - glyphy_arc_t *arc) -{ - *arc = Arc (*p0, *p1, 0); -} - -void -glyphy_arc_from_conic (const glyphy_point_t *p0, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - glyphy_arc_t *arc, - double *error) -{ - Point p1_ (Point (*p0).lerp (2/3., *p1)); - Point p2_ (Point (*p2).lerp (2/3., *p1)); - glyphy_arc_from_cubic (p0, - &p1_, - &p2_, - p2, - arc, - error); -} - -void -glyphy_arc_from_cubic (const glyphy_point_t *p0, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - const glyphy_point_t *p3, - glyphy_arc_t *arc, - double *error) -{ - *arc = ArcBezierApproximatorDefault::approximate_bezier_with_arc (Bezier (*p0, *p1, *p2, *p3), error); -} - -void -glyphy_arc_to_cubic (const glyphy_arc_t *arc, - glyphy_point_t *p0, - glyphy_point_t *p1, - glyphy_point_t *p2, - glyphy_point_t *p3, - double *error) -{ - Bezier b = Arc (*arc).approximate_bezier (error); - *p0 = arc->p0; - *p1 = b.p1; - *p2 = b.p2; - *p3 = arc->p1; -} diff --git a/src/glyphy-arcs-bezier.hh b/src/glyphy-arcs-bezier.hh deleted file mode 100644 index c09479f..0000000 --- a/src/glyphy-arcs-bezier.hh +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - -#ifndef GLYPHY_ARCS_BEZIER_HH -#define GLYPHY_ARCS_BEZIER_HH - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" -#include "glyphy-arc-bezier.hh" - -namespace GLyphy { -namespace ArcsBezier { - -using namespace Geometry; -using namespace ArcBezier; - -template -class ArcsBezierApproximatorSpringSystem -{ - static inline void calc_arcs (const Bezier &b, - const std::vector &t, - const ArcBezierApproximator &appx, - std::vector &e, - std::vector &arcs, - double &max_e, double &min_e) - { - unsigned int n = t.size () - 1; - e.resize (n); - arcs.clear (); - max_e = 0; - min_e = GLYPHY_INFINITY; - for (unsigned int i = 0; i < n; i++) - { - Bezier segment = b.segment (t[i], t[i + 1]); - arcs.push_back (appx.approximate_bezier_with_arc (segment, &e[i])); - - max_e = std::max (max_e, e[i]); - min_e = std::min (min_e, e[i]); - } - } - - static inline void jiggle (const Bezier &b, - const ArcBezierApproximator &appx, - std::vector &t, - std::vector &e, - std::vector &arcs, - double &max_e, double &min_e, - double tolerance, - unsigned int &n_jiggle) - { - unsigned int n = t.size () - 1; - double conditioner = tolerance * .01; - //fprintf (stderr, "candidate n %d max_e %g min_e %g\n", n, max_e, min_e); - unsigned int max_jiggle = log2 (n) + 1; - unsigned int s; - for (s = 0; s < max_jiggle; s++) - { - double total = 0; - for (unsigned int i = 0; i < n; i++) { - double l = t[i + 1] - t[i]; - double k_inv = l * pow (e[i] + conditioner, -.3); - total += k_inv; - e[i] = k_inv; - } - for (unsigned int i = 0; i < n; i++) { - double k_inv = e[i]; - double l = k_inv / total; - t[i + 1] = t[i] + l; - } - t[n] = 1.0; // Do this to get real 1.0, not .9999999999999998! - - calc_arcs (b, t, appx, e, arcs, max_e, min_e); - - //fprintf (stderr, "n %d jiggle %d max_e %g min_e %g\n", n, s, max_e, min_e); - - n_jiggle++; - if (max_e < tolerance || (2 * min_e - max_e > tolerance)) - break; - } - //if (s == max_jiggle) fprintf (stderr, "JIGGLE OVERFLOW n %d s %d\n", n, s); - } - - public: - static void approximate_bezier_with_arcs (const Bezier &b, - double tolerance, - const ArcBezierApproximator &appx, - std::vector &arcs, - double *perror, - unsigned int max_segments = 100) - { - /* Handle fully-degenerate cases. */ - Vector v1 (b.p1 - b.p0); - Vector v2 (b.p2 - b.p0); - Vector v3 (b.p3 - b.p0); - if (fabs (v1.cross(v2)) < GLYPHY_EPSILON && fabs (v2.cross(v3)) < GLYPHY_EPSILON) - { - /* Curve has no area. If endpoints are NOT the same, replace with single - * line segment. Otherwise fully skip. */ - arcs.clear (); - if (b.p0 != b.p1) - arcs.push_back (Arc (b.p0, b.p3, 0)); - return; - } - - std::vector t; - std::vector e; - double max_e = 0., min_e = 0.; - unsigned int n_jiggle = 0; - - /* Technically speaking we can bsearch for n. */ - for (unsigned int n = 1; n <= max_segments; n++) - { - t.resize (n + 1); - for (unsigned int i = 0; i < n; i++) - t[i] = double (i) / n; - t[n] = 1.0; // Do this out of the loop to get real 1.0, not .9999999999999998! - - calc_arcs (b, t, appx, e, arcs, max_e, min_e); - - for (unsigned int i = 0; i < n; i++) - if (e[i] <= tolerance) { - jiggle (b, appx, t, e, arcs, max_e, min_e, tolerance, n_jiggle); - break; - } - - if (max_e <= tolerance) - break; - } - if (perror) - *perror = max_e; - //fprintf (stderr, "n_jiggle %d\n", n_jiggle); - } -}; - -} /* namespace ArcsBezier */ -} /* namespace GLyphy */ - -#endif /* GLYPHY_ARCS_BEZIER_HH */ diff --git a/src/glyphy-arcs.cc b/src/glyphy-arcs.cc deleted file mode 100644 index 7479b7d..0000000 --- a/src/glyphy-arcs.cc +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" -#include "glyphy-arcs-bezier.hh" - -using namespace GLyphy::Geometry; -using namespace GLyphy::ArcsBezier; - - - -/* - * Approximate outlines with multiple arcs - */ - - -struct glyphy_arc_accumulator_t { - unsigned int refcount; - - double tolerance; - double max_d; - unsigned int d_bits; - glyphy_arc_endpoint_accumulator_callback_t callback; - void *user_data; - - glyphy_point_t start_point; - glyphy_point_t current_point; - bool need_moveto; - unsigned int num_endpoints; - double max_error; - glyphy_bool_t success; -}; - - -glyphy_arc_accumulator_t * -glyphy_arc_accumulator_create (void) -{ - glyphy_arc_accumulator_t *acc = (glyphy_arc_accumulator_t *) calloc (1, sizeof (glyphy_arc_accumulator_t)); - acc->refcount = 1; - - acc->tolerance = 5e-4; - acc->max_d = GLYPHY_MAX_D; - acc->d_bits = 8; - acc->callback = NULL; - acc->user_data = NULL; - - glyphy_arc_accumulator_reset (acc); - - return acc; -} - -void -glyphy_arc_accumulator_reset (glyphy_arc_accumulator_t *acc) -{ - acc->start_point = acc->current_point = Point (0, 0); - acc->need_moveto = true; - acc->num_endpoints = 0; - acc->max_error = 0; - acc->success = true; -} - -void -glyphy_arc_accumulator_destroy (glyphy_arc_accumulator_t *acc) -{ - if (!acc || --acc->refcount) - return; - - free (acc); -} - -glyphy_arc_accumulator_t * -glyphy_arc_accumulator_reference (glyphy_arc_accumulator_t *acc) -{ - if (acc) - acc->refcount++; - return acc; -} - - -/* Configure acc */ - -void -glyphy_arc_accumulator_set_tolerance (glyphy_arc_accumulator_t *acc, - double tolerance) -{ - acc->tolerance = tolerance; -} - -double -glyphy_arc_accumulator_get_tolerance (glyphy_arc_accumulator_t *acc) -{ - return acc->tolerance; -} - -void -glyphy_arc_accumulator_set_callback (glyphy_arc_accumulator_t *acc, - glyphy_arc_endpoint_accumulator_callback_t callback, - void *user_data) -{ - acc->callback = callback; - acc->user_data = user_data; -} - -void -glyphy_arc_accumulator_get_callback (glyphy_arc_accumulator_t *acc, - glyphy_arc_endpoint_accumulator_callback_t *callback, - void **user_data) -{ - *callback = acc->callback; - *user_data = acc->user_data; -} - -void -glyphy_arc_accumulator_set_d_metrics (glyphy_arc_accumulator_t *acc, - double max_d, - double d_bits) -{ - acc->max_d = max_d; - acc->d_bits = d_bits; -} - -void -glyphy_arc_accumulator_get_d_metrics (glyphy_arc_accumulator_t *acc, - double *max_d, - double *d_bits) -{ - *max_d = acc->max_d; - *d_bits = acc->d_bits; -} - - -/* Accumulation results */ - -unsigned int -glyphy_arc_accumulator_get_num_endpoints (glyphy_arc_accumulator_t *acc) -{ - return acc->num_endpoints; -} - -double -glyphy_arc_accumulator_get_error (glyphy_arc_accumulator_t *acc) -{ - return acc->max_error; -} - -glyphy_bool_t -glyphy_arc_accumulator_successful (glyphy_arc_accumulator_t *acc) -{ - return acc->success; -} - - -/* Accumulate */ - -static void -emit (glyphy_arc_accumulator_t *acc, const Point &p, double d) -{ - glyphy_arc_endpoint_t endpoint = {p, d}; - acc->success = acc->success && acc->callback (&endpoint, acc->user_data); - if (acc->success) { - acc->num_endpoints++; - acc->current_point = p; - } -} - -static void -accumulate (glyphy_arc_accumulator_t *acc, const Point &p, double d) -{ - if (Point (acc->current_point) == p) - return; - if (d == GLYPHY_INFINITY) { - /* Emit moveto lazily, for cleaner outlines */ - acc->need_moveto = true; - acc->current_point = p; - return; - } - if (acc->need_moveto) { - emit (acc, acc->current_point, GLYPHY_INFINITY); - if (acc->success) { - acc->start_point = acc->current_point; - acc->need_moveto = false; - } - } - emit (acc, p, d); -} - -static void -move_to (glyphy_arc_accumulator_t *acc, const Point &p) -{ - if (!acc->num_endpoints || p != acc->current_point) - accumulate (acc, p, GLYPHY_INFINITY); -} - -static void -arc_to (glyphy_arc_accumulator_t *acc, const Point &p1, double d) -{ - accumulate (acc, p1, d); -} - -static void -bezier (glyphy_arc_accumulator_t *acc, const Bezier &b) -{ - double e = 0; - - std::vector arcs; - typedef ArcBezierApproximatorQuantizedDefault _ArcBezierApproximator; - _ArcBezierApproximator appx (acc->max_d, acc->d_bits); - ArcsBezierApproximatorSpringSystem<_ArcBezierApproximator> - ::approximate_bezier_with_arcs (b, acc->tolerance, appx, arcs, &e); - - acc->max_error = std::max (acc->max_error, e); - - move_to (acc, b.p0); - for (unsigned int i = 0; i < arcs.size (); i++) - arc_to (acc, arcs[i].p1, arcs[i].d); -} - -static void -close_path (glyphy_arc_accumulator_t *acc) -{ - if (!acc->need_moveto && Point (acc->current_point) != Point (acc->start_point)) - arc_to (acc, acc->start_point, 0); -} - -void -glyphy_arc_accumulator_move_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p0) -{ - move_to (acc, *p0); -} - -void -glyphy_arc_accumulator_line_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1) -{ - arc_to (acc, *p1, 0); -} - -void -glyphy_arc_accumulator_conic_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - const glyphy_point_t *p2) -{ - bezier (acc, Bezier (acc->current_point, - Point (acc->current_point).lerp (2/3., *p1), - Point (*p2).lerp (2/3., *p1), - *p2)); -} - -void -glyphy_arc_accumulator_cubic_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - const glyphy_point_t *p2, - const glyphy_point_t *p3) -{ - bezier (acc, Bezier (acc->current_point, *p1, *p2, *p3)); -} - -void -glyphy_arc_accumulator_arc_to (glyphy_arc_accumulator_t *acc, - const glyphy_point_t *p1, - double d) -{ - arc_to (acc, *p1, d); -} - -void -glyphy_arc_accumulator_close_path (glyphy_arc_accumulator_t *acc) -{ - close_path (acc); -} - - - -/* - * Outline extents from arc list - */ - - -void -glyphy_arc_list_extents (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_extents_t *extents) -{ - Point p0 (0, 0); - glyphy_extents_clear (extents); - for (unsigned int i = 0; i < num_endpoints; i++) { - const glyphy_arc_endpoint_t &endpoint = endpoints[i]; - if (endpoint.d == GLYPHY_INFINITY) { - p0 = endpoint.p; - continue; - } - Arc arc (p0, endpoint.p, endpoint.d); - p0 = endpoint.p; - - glyphy_extents_t arc_extents; - arc.extents (arc_extents); - glyphy_extents_extend (extents, &arc_extents); - } -} diff --git a/src/glyphy-blob.cc b/src/glyphy-blob.cc deleted file mode 100644 index b10fae8..0000000 --- a/src/glyphy-blob.cc +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" - -#define MAX_GRID_SIZE 63 - -using namespace GLyphy::Geometry; - - -#define UPPER_BITS(v,bits,total_bits) ((v) >> ((total_bits) - (bits))) -#define LOWER_BITS(v,bits,total_bits) ((v) & ((1 << (bits)) - 1)) - -#define MAX_X 4095 -#define MAX_Y 4095 - -static inline glyphy_rgba_t -arc_endpoint_encode (unsigned int ix, unsigned int iy, double d) -{ - glyphy_rgba_t v; - - /* 12 bits for each of x and y, 8 bits for d */ - assert (ix <= MAX_X); - assert (iy <= MAX_Y); - unsigned int id; - if (isinf (d)) - id = 0; - else { - assert (fabs (d) <= GLYPHY_MAX_D); - id = 128 + lround (d * 127 / GLYPHY_MAX_D); - } - assert (id < 256); - - v.r = id; - v.g = LOWER_BITS (ix, 8, 12); - v.b = LOWER_BITS (iy, 8, 12); - v.a = ((ix >> 8) << 4) | (iy >> 8); - return v; -} - -static inline glyphy_rgba_t -arc_list_encode (unsigned int offset, unsigned int num_points, int side) -{ - glyphy_rgba_t v; - v.r = 0; // unused for arc-list encoding - v.g = UPPER_BITS (offset, 8, 16); - v.b = LOWER_BITS (offset, 8, 16); - v.a = LOWER_BITS (num_points, 8, 8); - if (side < 0 && !num_points) - v.a = 255; - return v; -} - -static inline glyphy_rgba_t -line_encode (const Line &line) -{ - Line l = line.normalized (); - double angle = l.n.angle (); - double distance = l.c; - - int ia = lround (-angle / M_PI * 0x7FFF); - unsigned int ua = ia + 0x8000; - assert (0 == (ua & ~0xFFFF)); - - int id = lround (distance * 0x1FFF); - unsigned int ud = id + 0x4000; - assert (0 == (ud & ~0x7FFF)); - - /* Marker for line-encoded */ - ud |= 0x8000; - - glyphy_rgba_t v; - v.r = ud >> 8; - v.g = ud & 0xFF; - v.b = ua >> 8; - v.a = ua & 0xFF; - return v; -} - - -/* Given a cell, fills the vector closest_arcs with arcs that may be closest to some point in the cell. - * Uses idea that all close arcs to cell must be ~close to center of cell. - */ -static void -closest_arcs_to_cell (Point c0, Point c1, /* corners */ - double faraway, - double enlighten_max, - double embolden_max, - const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - std::vector &near_endpoints, - int *side) -{ - // This can be improved: - double synth_max = std::max (enlighten_max, embolden_max); - faraway = std::max (faraway, synth_max); - - // Find distance between cell center - Point c = c0.midpoint (c1); - double min_dist = glyphy_sdf_from_arc_list (endpoints, num_endpoints, &c, NULL); - - *side = min_dist >= 0 ? +1 : -1; - min_dist = fabs (min_dist); - std::vector near_arcs; - - // If d is the distance from the center of the square to the nearest arc, then - // all nearest arcs to the square must be at most almost [d + half_diagonal] from the center. - double half_diagonal = (c - c0).len (); - double added = min_dist + half_diagonal + synth_max; - double radius_squared = added * added; - if (min_dist - half_diagonal <= faraway) { - Point p0 (0, 0); - for (unsigned int i = 0; i < num_endpoints; i++) { - const glyphy_arc_endpoint_t &endpoint = endpoints[i]; - if (endpoint.d == GLYPHY_INFINITY) { - p0 = endpoint.p; - continue; - } - Arc arc (p0, endpoint.p, endpoint.d); - p0 = endpoint.p; - - if (arc.squared_distance_to_point (c) <= radius_squared) - near_arcs.push_back (arc); - } - } - - Point p1 = Point (0, 0); - for (unsigned i = 0; i < near_arcs.size (); i++) - { - Arc arc = near_arcs[i]; - - if (i == 0 || p1 != arc.p0) { - glyphy_arc_endpoint_t endpoint = {arc.p0, GLYPHY_INFINITY}; - near_endpoints.push_back (endpoint); - p1 = arc.p0; - } - - glyphy_arc_endpoint_t endpoint = {arc.p1, arc.d}; - near_endpoints.push_back (endpoint); - p1 = arc.p1; - } -} - - -glyphy_bool_t -glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_rgba_t *blob, - unsigned int blob_size, - double faraway, - double avg_fetch_desired, - double *avg_fetch_achieved, - unsigned int *output_len, - unsigned int *nominal_width, /* 8bit */ - unsigned int *nominal_height, /* 8bit */ - glyphy_extents_t *pextents) -{ - return - glyphy_arc_list_encode_blob2 (endpoints, - num_endpoints, - blob, - blob_size, - faraway, - faraway / M_SQRT2, - 0, - 0, - avg_fetch_achieved, - output_len, - nominal_width, /* 6bit */ - nominal_height, /* 6bit */ - pextents); -} - -GLYPHY_API glyphy_bool_t -glyphy_arc_list_encode_blob2 (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_rgba_t *blob, - unsigned int blob_size, - double faraway, - double grid_unit, - double enlighten_max, - double embolden_max, - double *avg_fetch_achieved, - unsigned int *output_len, - unsigned int *nominal_width, /* 6bit */ - unsigned int *nominal_height, /* 6bit */ - glyphy_extents_t *pextents) -{ - glyphy_extents_t extents; - glyphy_extents_clear (&extents); - - glyphy_arc_list_extents (endpoints, num_endpoints, &extents); - - if (glyphy_extents_is_empty (&extents)) { - *pextents = extents; - if (!blob_size) - return false; - *blob = arc_list_encode (0, 0, +1); - *avg_fetch_achieved = 1; - *output_len = 1; - *nominal_width = *nominal_height = 1; - return true; - } - - /* Add antialiasing padding */ - extents.min_x -= faraway + embolden_max; - extents.min_y -= faraway + embolden_max; - extents.max_x += faraway + embolden_max; - extents.max_y += faraway + embolden_max; - - double glyph_width = extents.max_x - extents.min_x; - double glyph_height = extents.max_y - extents.min_y; - double unit = std::max (glyph_width, glyph_height); - - unsigned int grid_w = std::min (MAX_GRID_SIZE, (int) std::ceil (glyph_width / grid_unit)); - unsigned int grid_h = std::min (MAX_GRID_SIZE, (int) std::ceil (glyph_height / grid_unit)); - - if (glyph_width > glyph_height) { - glyph_height = grid_h * unit / grid_w; - extents.max_y = extents.min_y + glyph_height; - } else { - glyph_width = grid_w * unit / grid_h; - extents.max_x = extents.min_x + glyph_width; - } - - double cell_unit = unit / std::max (grid_w, grid_h); - - std::vector tex_data; - std::vector near_endpoints; - - unsigned int header_length = grid_w * grid_h; - unsigned int offset = header_length; - tex_data.resize (header_length); - Point origin = Point (extents.min_x, extents.min_y); - unsigned int total_arcs = 0; - - for (unsigned int row = 0; row < grid_h; row++) - for (unsigned int col = 0; col < grid_w; col++) - { - Point cp0 = origin + Vector ((col + 0) * cell_unit, (row + 0) * cell_unit); - Point cp1 = origin + Vector ((col + 1) * cell_unit, (row + 1) * cell_unit); - near_endpoints.clear (); - - int side; - closest_arcs_to_cell (cp0, cp1, - faraway, - enlighten_max, - embolden_max, - endpoints, num_endpoints, - near_endpoints, - &side); - -#define QUANTIZE_X(X) (lround (MAX_X * ((X - extents.min_x) / glyph_width ))) -#define QUANTIZE_Y(Y) (lround (MAX_Y * ((Y - extents.min_y) / glyph_height))) -#define DEQUANTIZE_X(X) (double (X) / MAX_X * glyph_width + extents.min_x) -#define DEQUANTIZE_Y(Y) (double (Y) / MAX_Y * glyph_height + extents.min_y) -#define SNAP(P) (Point (DEQUANTIZE_X (QUANTIZE_X ((P).x)), DEQUANTIZE_Y (QUANTIZE_Y ((P).y)))) - - if (near_endpoints.size () == 2 && near_endpoints[1].d == 0) { - Point c (extents.min_x + glyph_width * .5, extents.min_y + glyph_height * .5); - Line line (SNAP (near_endpoints[0].p), SNAP (near_endpoints[1].p)); - line.c -= line.n * Vector (c); - line.c /= unit; - tex_data[row * grid_w + col] = line_encode (line); - continue; - } - - /* If the arclist is two arcs that can be combined in encoding if reordered, - * do that. */ - if (near_endpoints.size () == 4 && - isinf (near_endpoints[2].d) && - near_endpoints[0].p.x == near_endpoints[3].p.x && - near_endpoints[0].p.y == near_endpoints[3].p.y) - { - glyphy_arc_endpoint_t e0, e1, e2; - e0 = near_endpoints[2]; - e1 = near_endpoints[3]; - e2 = near_endpoints[1]; - near_endpoints.resize (0); - near_endpoints.push_back (e0); - near_endpoints.push_back (e1); - near_endpoints.push_back (e2); - } - - for (unsigned i = 0; i < near_endpoints.size (); i++) { - glyphy_arc_endpoint_t &endpoint = near_endpoints[i]; - tex_data.push_back (arc_endpoint_encode (QUANTIZE_X(endpoint.p.x), QUANTIZE_Y(endpoint.p.y), endpoint.d)); - } - - unsigned int current_endpoints = tex_data.size () - offset; - - if (current_endpoints) - { - /* See if we can fulfill this cell by using already-encoded arcs */ - const glyphy_rgba_t *needle = &tex_data[offset]; - unsigned int needle_len = current_endpoints; - const glyphy_rgba_t *haystack = &tex_data[header_length]; - unsigned int haystack_len = offset - header_length; - - bool found = false; - while (haystack_len >= needle_len) { - /* Trick: we don't care about first endpoint's d value, so skip one - * byte in comparison. This works because arc_encode() packs the - * d value in the first byte. */ - if (0 == memcmp (1 + (const char *) needle, - 1 + (const char *) haystack, - needle_len * sizeof (*needle) - 1)) { - found = true; - break; - } - haystack++; - haystack_len--; - } - if (found) { - unsigned int new_offset = haystack - &tex_data[0]; - tex_data.resize (offset); - haystack = needle = NULL; /* Invalidated by the resize. */ - offset = new_offset; - } - } - else - offset = 0; - - tex_data[row * grid_w + col] = arc_list_encode (offset, current_endpoints, side); - offset = tex_data.size (); - - total_arcs += current_endpoints; - } - - if (avg_fetch_achieved) - *avg_fetch_achieved = 1 + double (total_arcs) / (grid_w * grid_h); - - *pextents = extents; - - *output_len = tex_data.size (); - *nominal_width = grid_w; - *nominal_height = grid_h; - - if (tex_data.size () > blob_size) - return false; - - memcpy (blob, &tex_data[0], tex_data.size () * sizeof(tex_data[0])); - - return true; -} diff --git a/src/glyphy-common-glsl.h b/src/glyphy-common-glsl.h deleted file mode 100644 index 868177b..0000000 --- a/src/glyphy-common-glsl.h +++ /dev/null @@ -1,224 +0,0 @@ -static const char *glyphy_common_glsl = -"/*\n" -" * Copyright 2012 Google, Inc. All Rights Reserved.\n" -" *\n" -" * Licensed under the Apache License, Version 2.0 (the \"License\");\n" -" * you may not use this file except in compliance with the License.\n" -" * You may obtain a copy of the License at\n" -" *\n" -" * http://www.apache.org/licenses/LICENSE-2.0\n" -" *\n" -" * Unless required by applicable law or agreed to in writing, software\n" -" * distributed under the License is distributed on an \"AS IS\" BASIS,\n" -" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" -" * See the License for the specific language governing permissions and\n" -" * limitations under the License.\n" -" *\n" -" * Google Author(s): Behdad Esfahbod, Maysum Panju\n" -" */\n" -"\n" -"\n" -"#ifndef GLYPHY_INFINITY\n" -"# define GLYPHY_INFINITY 1e9\n" -"#endif\n" -"#ifndef GLYPHY_EPSILON\n" -"# define GLYPHY_EPSILON 1e-5\n" -"#endif\n" -"\n" -"#ifndef GLYPHY_RGBA\n" -"# ifdef GLYPHY_BGRA\n" -"# define GLYPHY_RGBA(v) glyphy_bgra (v)\n" -"# else\n" -"# define GLYPHY_RGBA(v) glyphy_rgba (v)\n" -"# endif\n" -"#endif\n" -"\n" -"vec4\n" -"glyphy_rgba (const vec4 v)\n" -"{\n" -" return v.rgba;\n" -"}\n" -"\n" -"vec4\n" -"glyphy_bgra (const vec4 v)\n" -"{\n" -" return v.bgra;\n" -"}\n" -"\n" -"\n" -"struct glyphy_arc_t {\n" -" vec2 p0;\n" -" vec2 p1;\n" -" float d;\n" -"};\n" -"\n" -"struct glyphy_arc_endpoint_t {\n" -" /* Second arc endpoint */\n" -" vec2 p;\n" -" /* Infinity if this endpoint does not form an arc with the previous\n" -" * endpoint. Ie. a \"move_to\". Test with glyphy_isinf().\n" -" * Arc depth otherwise. */\n" -" float d;\n" -"};\n" -"\n" -"struct glyphy_arc_list_t {\n" -" /* Number of endpoints in the list.\n" -" * Will be zero if we're far away inside or outside, in which case side is set.\n" -" * Will be -1 if this arc-list encodes a single line, in which case line_* are set. */\n" -" int num_endpoints;\n" -"\n" -" /* If num_endpoints is zero, this specifies whether we are inside (-1)\n" -" * or outside (+1). Otherwise we're unsure (0). */\n" -" int side;\n" -" /* Offset to the arc-endpoints from the beginning of the glyph blob */\n" -" int offset;\n" -"\n" -" /* A single line is all we care about. It's right here. */\n" -" float line_angle;\n" -" float line_distance; /* From nominal glyph center */\n" -"};\n" -"\n" -"bool\n" -"glyphy_isinf (const float v)\n" -"{\n" -" return abs (v) >= GLYPHY_INFINITY * .5;\n" -"}\n" -"\n" -"bool\n" -"glyphy_iszero (const float v)\n" -"{\n" -" return abs (v) <= GLYPHY_EPSILON * 2.;\n" -"}\n" -"\n" -"vec2\n" -"glyphy_ortho (const vec2 v)\n" -"{\n" -" return vec2 (-v.y, v.x);\n" -"}\n" -"\n" -"int\n" -"glyphy_float_to_byte (const float v)\n" -"{\n" -" return int (v * (256. - GLYPHY_EPSILON));\n" -"}\n" -"\n" -"ivec4\n" -"glyphy_vec4_to_bytes (const vec4 v)\n" -"{\n" -" return ivec4 (v * (256. - GLYPHY_EPSILON));\n" -"}\n" -"\n" -"ivec2\n" -"glyphy_float_to_two_nimbles (const float v)\n" -"{\n" -" int f = glyphy_float_to_byte (v);\n" -" return ivec2 (f / 16, int(mod (float(f), 16.)));\n" -"}\n" -"\n" -"/* returns tan (2 * atan (d)) */\n" -"float\n" -"glyphy_tan2atan (const float d)\n" -"{\n" -" return 2. * d / (1. - d * d);\n" -"}\n" -"\n" -"glyphy_arc_endpoint_t\n" -"glyphy_arc_endpoint_decode (const vec4 v, const ivec2 nominal_size)\n" -"{\n" -" vec2 p = (vec2 (glyphy_float_to_two_nimbles (v.a)) + v.gb) / 16.;\n" -" float d = v.r;\n" -" if (d == 0.)\n" -" d = GLYPHY_INFINITY;\n" -" else\n" -"#define GLYPHY_MAX_D .5\n" -" d = float(glyphy_float_to_byte (d) - 128) * GLYPHY_MAX_D / 127.;\n" -"#undef GLYPHY_MAX_D\n" -" return glyphy_arc_endpoint_t (p * vec2(nominal_size), d);\n" -"}\n" -"\n" -"vec2\n" -"glyphy_arc_center (const glyphy_arc_t a)\n" -"{\n" -" return mix (a.p0, a.p1, .5) +\n" -" glyphy_ortho (a.p1 - a.p0) / (2. * glyphy_tan2atan (a.d));\n" -"}\n" -"\n" -"bool\n" -"glyphy_arc_wedge_contains (const glyphy_arc_t a, const vec2 p)\n" -"{\n" -" float d2 = glyphy_tan2atan (a.d);\n" -" return dot (p - a.p0, (a.p1 - a.p0) * mat2(1, d2, -d2, 1)) >= 0. &&\n" -" dot (p - a.p1, (a.p1 - a.p0) * mat2(1, -d2, d2, 1)) <= 0.;\n" -"}\n" -"\n" -"float\n" -"glyphy_arc_wedge_signed_dist_shallow (const glyphy_arc_t a, const vec2 p)\n" -"{\n" -" vec2 v = normalize (a.p1 - a.p0);\n" -" float line_d = dot (p - a.p0, glyphy_ortho (v));\n" -" if (a.d == 0.)\n" -" return line_d;\n" -"\n" -" float d0 = dot ((p - a.p0), v);\n" -" if (d0 < 0.)\n" -" return sign (line_d) * distance (p, a.p0);\n" -" float d1 = dot ((a.p1 - p), v);\n" -" if (d1 < 0.)\n" -" return sign (line_d) * distance (p, a.p1);\n" -" float r = 2. * a.d * (d0 * d1) / (d0 + d1);\n" -" if (r * line_d > 0.)\n" -" return sign (line_d) * min (abs (line_d + r), min (distance (p, a.p0), distance (p, a.p1)));\n" -" return line_d + r;\n" -"}\n" -"\n" -"float\n" -"glyphy_arc_wedge_signed_dist (const glyphy_arc_t a, const vec2 p)\n" -"{\n" -" if (abs (a.d) <= .03)\n" -" return glyphy_arc_wedge_signed_dist_shallow (a, p);\n" -" vec2 c = glyphy_arc_center (a);\n" -" return sign (a.d) * (distance (a.p0, c) - distance (p, c));\n" -"}\n" -"\n" -"float\n" -"glyphy_arc_extended_dist (const glyphy_arc_t a, const vec2 p)\n" -"{\n" -" /* Note: this doesn't handle points inside the wedge. */\n" -" vec2 m = mix (a.p0, a.p1, .5);\n" -" float d2 = glyphy_tan2atan (a.d);\n" -" if (dot (p - m, a.p1 - m) < 0.)\n" -" return dot (p - a.p0, normalize ((a.p1 - a.p0) * mat2(+d2, -1, +1, +d2)));\n" -" else\n" -" return dot (p - a.p1, normalize ((a.p1 - a.p0) * mat2(-d2, -1, +1, -d2)));\n" -"}\n" -"\n" -"int\n" -"glyphy_arc_list_offset (const vec2 p, const ivec2 nominal_size)\n" -"{\n" -" ivec2 cell = ivec2 (clamp (floor (p), vec2 (0.,0.), vec2(nominal_size - 1)));\n" -" return cell.y * nominal_size.x + cell.x;\n" -"}\n" -"\n" -"glyphy_arc_list_t\n" -"glyphy_arc_list_decode (const vec4 v, const ivec2 nominal_size)\n" -"{\n" -" glyphy_arc_list_t l;\n" -" ivec4 iv = glyphy_vec4_to_bytes (v);\n" -" l.side = 0; /* unsure */\n" -" if (iv.r == 0) { /* arc-list encoded */\n" -" l.offset = (iv.g * 256) + iv.b;\n" -" l.num_endpoints = iv.a;\n" -" if (l.num_endpoints == 255) {\n" -" l.num_endpoints = 0;\n" -" l.side = -1;\n" -" } else if (l.num_endpoints == 0)\n" -" l.side = +1;\n" -" } else { /* single line encoded */\n" -" l.num_endpoints = -1;\n" -" l.line_distance = float(((iv.r - 128) * 256 + iv.g) - 0x4000) / float (0x1FFF)\n" -" * max (float (nominal_size.x), float (nominal_size.y));\n" -" l.line_angle = float(-((iv.b * 256 + iv.a) - 0x8000)) / float (0x7FFF) * 3.14159265358979;\n" -" }\n" -" return l;\n" -"}\n" -; diff --git a/src/glyphy-common.glsl b/src/glyphy-common.glsl deleted file mode 100644 index 792b1cf..0000000 --- a/src/glyphy-common.glsl +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - - -#ifndef GLYPHY_INFINITY -# define GLYPHY_INFINITY 1e9 -#endif -#ifndef GLYPHY_EPSILON -# define GLYPHY_EPSILON 1e-5 -#endif - -#ifndef GLYPHY_RGBA -# ifdef GLYPHY_BGRA -# define GLYPHY_RGBA(v) glyphy_bgra (v) -# else -# define GLYPHY_RGBA(v) glyphy_rgba (v) -# endif -#endif - -vec4 -glyphy_rgba (const vec4 v) -{ - return v.rgba; -} - -vec4 -glyphy_bgra (const vec4 v) -{ - return v.bgra; -} - - -struct glyphy_arc_t { - vec2 p0; - vec2 p1; - float d; -}; - -struct glyphy_arc_endpoint_t { - /* Second arc endpoint */ - vec2 p; - /* Infinity if this endpoint does not form an arc with the previous - * endpoint. Ie. a "move_to". Test with glyphy_isinf(). - * Arc depth otherwise. */ - float d; -}; - -struct glyphy_arc_list_t { - /* Number of endpoints in the list. - * Will be zero if we're far away inside or outside, in which case side is set. - * Will be -1 if this arc-list encodes a single line, in which case line_* are set. */ - int num_endpoints; - - /* If num_endpoints is zero, this specifies whether we are inside (-1) - * or outside (+1). Otherwise we're unsure (0). */ - int side; - /* Offset to the arc-endpoints from the beginning of the glyph blob */ - int offset; - - /* A single line is all we care about. It's right here. */ - float line_angle; - float line_distance; /* From nominal glyph center */ -}; - -bool -glyphy_isinf (const float v) -{ - return abs (v) >= GLYPHY_INFINITY * .5; -} - -bool -glyphy_iszero (const float v) -{ - return abs (v) <= GLYPHY_EPSILON * 2.; -} - -vec2 -glyphy_ortho (const vec2 v) -{ - return vec2 (-v.y, v.x); -} - -int -glyphy_float_to_byte (const float v) -{ - return int (v * (256. - GLYPHY_EPSILON)); -} - -ivec4 -glyphy_vec4_to_bytes (const vec4 v) -{ - return ivec4 (v * (256. - GLYPHY_EPSILON)); -} - -ivec2 -glyphy_float_to_two_nimbles (const float v) -{ - int f = glyphy_float_to_byte (v); - return ivec2 (f / 16, int(mod (float(f), 16.))); -} - -/* returns tan (2 * atan (d)) */ -float -glyphy_tan2atan (const float d) -{ - return 2. * d / (1. - d * d); -} - -glyphy_arc_endpoint_t -glyphy_arc_endpoint_decode (const vec4 v, const ivec2 nominal_size) -{ - vec2 p = (vec2 (glyphy_float_to_two_nimbles (v.a)) + v.gb) / 16.; - float d = v.r; - if (d == 0.) - d = GLYPHY_INFINITY; - else -#define GLYPHY_MAX_D .5 - d = float(glyphy_float_to_byte (d) - 128) * GLYPHY_MAX_D / 127.; -#undef GLYPHY_MAX_D - return glyphy_arc_endpoint_t (p * vec2(nominal_size), d); -} - -vec2 -glyphy_arc_center (const glyphy_arc_t a) -{ - return mix (a.p0, a.p1, .5) + - glyphy_ortho (a.p1 - a.p0) / (2. * glyphy_tan2atan (a.d)); -} - -bool -glyphy_arc_wedge_contains (const glyphy_arc_t a, const vec2 p) -{ - float d2 = glyphy_tan2atan (a.d); - return dot (p - a.p0, (a.p1 - a.p0) * mat2(1, d2, -d2, 1)) >= 0. && - dot (p - a.p1, (a.p1 - a.p0) * mat2(1, -d2, d2, 1)) <= 0.; -} - -float -glyphy_arc_wedge_signed_dist_shallow (const glyphy_arc_t a, const vec2 p) -{ - vec2 v = normalize (a.p1 - a.p0); - float line_d = dot (p - a.p0, glyphy_ortho (v)); - if (a.d == 0.) - return line_d; - - float d0 = dot ((p - a.p0), v); - if (d0 < 0.) - return sign (line_d) * distance (p, a.p0); - float d1 = dot ((a.p1 - p), v); - if (d1 < 0.) - return sign (line_d) * distance (p, a.p1); - float r = 2. * a.d * (d0 * d1) / (d0 + d1); - if (r * line_d > 0.) - return sign (line_d) * min (abs (line_d + r), min (distance (p, a.p0), distance (p, a.p1))); - return line_d + r; -} - -float -glyphy_arc_wedge_signed_dist (const glyphy_arc_t a, const vec2 p) -{ - if (abs (a.d) <= .03) - return glyphy_arc_wedge_signed_dist_shallow (a, p); - vec2 c = glyphy_arc_center (a); - return sign (a.d) * (distance (a.p0, c) - distance (p, c)); -} - -float -glyphy_arc_extended_dist (const glyphy_arc_t a, const vec2 p) -{ - /* Note: this doesn't handle points inside the wedge. */ - vec2 m = mix (a.p0, a.p1, .5); - float d2 = glyphy_tan2atan (a.d); - if (dot (p - m, a.p1 - m) < 0.) - return dot (p - a.p0, normalize ((a.p1 - a.p0) * mat2(+d2, -1, +1, +d2))); - else - return dot (p - a.p1, normalize ((a.p1 - a.p0) * mat2(-d2, -1, +1, -d2))); -} - -int -glyphy_arc_list_offset (const vec2 p, const ivec2 nominal_size) -{ - ivec2 cell = ivec2 (clamp (floor (p), vec2 (0.,0.), vec2(nominal_size - 1))); - return cell.y * nominal_size.x + cell.x; -} - -glyphy_arc_list_t -glyphy_arc_list_decode (const vec4 v, const ivec2 nominal_size) -{ - glyphy_arc_list_t l; - ivec4 iv = glyphy_vec4_to_bytes (v); - l.side = 0; /* unsure */ - if (iv.r == 0) { /* arc-list encoded */ - l.offset = (iv.g * 256) + iv.b; - l.num_endpoints = iv.a; - if (l.num_endpoints == 255) { - l.num_endpoints = 0; - l.side = -1; - } else if (l.num_endpoints == 0) - l.side = +1; - } else { /* single line encoded */ - l.num_endpoints = -1; - l.line_distance = float(((iv.r - 128) * 256 + iv.g) - 0x4000) / float (0x1FFF) - * max (float (nominal_size.x), float (nominal_size.y)); - l.line_angle = float(-((iv.b * 256 + iv.a) - 0x8000)) / float (0x7FFF) * 3.14159265358979; - } - return l; -} diff --git a/src/glyphy-common.hh b/src/glyphy-common.hh deleted file mode 100644 index 7b9c2e6..0000000 --- a/src/glyphy-common.hh +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - -#ifndef GLYPHY_COMMON_HH -#define GLYPHY_COMMON_HH - -#include - -#include -#include -#include -#include -#include -#include -#include - -#ifndef GLYPHY_EPSILON -# define GLYPHY_EPSILON 1e-5 -#endif -#ifndef GLYPHY_INFINITY -# define GLYPHY_INFINITY INFINITY -#endif - - -static inline bool -iszero (double v) -{ - return fabs (v) < 2 * GLYPHY_EPSILON; -} - - -#define GLYPHY_MAX_D .5 - -#undef ARRAY_LENGTH -#define ARRAY_LENGTH(__array) ((signed int) (sizeof (__array) / sizeof (__array[0]))) - -#define _ASSERT_STATIC1(_line, _cond) typedef int _static_assert_on_line_##_line##_failed[(_cond)?1:-1] -#define _ASSERT_STATIC0(_line, _cond) _ASSERT_STATIC1 (_line, (_cond)) -#define ASSERT_STATIC(_cond) _ASSERT_STATIC0 (__LINE__, (_cond)) - -#ifdef __ANDROID__ -#define log2(x) (log(x) / log(2.0)) -#endif - -#endif /* GLYPHY_COMMON_HH */ diff --git a/src/glyphy-extents.cc b/src/glyphy-extents.cc index 89ebfe2..5157c46 100644 --- a/src/glyphy-extents.cc +++ b/src/glyphy-extents.cc @@ -20,22 +20,25 @@ #include #endif -#include "glyphy-common.hh" +#include "glyphy.h" + +#include +#include void glyphy_extents_clear (glyphy_extents_t *extents) { - extents->min_x = GLYPHY_INFINITY; - extents->min_y = GLYPHY_INFINITY; - extents->max_x = -GLYPHY_INFINITY; - extents->max_y = -GLYPHY_INFINITY; + extents->min_x = INFINITY; + extents->min_y = INFINITY; + extents->max_x = -INFINITY; + extents->max_y = -INFINITY; } glyphy_bool_t glyphy_extents_is_empty (const glyphy_extents_t *extents) { - return isinf (extents->min_x); + return std::isinf (extents->min_x); } void diff --git a/src/glyphy-geometry.hh b/src/glyphy-geometry.hh deleted file mode 100644 index 3c60856..0000000 --- a/src/glyphy-geometry.hh +++ /dev/null @@ -1,742 +0,0 @@ -/* - * Copyright 2012,2013 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - -#ifndef GLYPHY_GEOMETRY_HH -#define GLYPHY_GEOMETRY_HH - -#include "glyphy-common.hh" - -namespace GLyphy { -namespace Geometry { - -template struct Pair; -struct Vector; -struct SignedVector; -struct Point; -struct Line; -struct Segment; -struct Arc; -struct Bezier; - -/* returns tan (2 * atan (d)) */ -inline double tan2atan (double d) { return 2 * d / (1 - d*d); } - -/* returns sin (2 * atan (d)) */ -inline double sin2atan (double d) { return 2 * d / (1 + d*d); } - -/* returns cos (2 * atan (d)) */ -inline double cos2atan (double d) { return (1 - d*d) / (1 + d*d); } - -template -struct Pair { - typedef Type ElementType; - - inline Pair (const Type &first_, const Type &second_) : first (first_), second (second_) {} - - Type first, second; -}; - -struct Point : glyphy_point_t { - inline Point (double x_, double y_) { x = x_; y = y_; } - inline explicit Point (const Vector &v); - inline Point (const glyphy_point_t &p) { *(glyphy_point_t *)this = p; } - - inline bool operator == (const Point &p) const; - inline bool operator != (const Point &p) const; - inline Point& operator+= (const Vector &v); - inline Point& operator-= (const Vector &v); - inline const Point operator+ (const Vector &v) const; - inline const Point operator- (const Vector &v) const; - inline const Vector operator- (const Point &p) const; - inline const Point midpoint (const Point &p) const; - inline const Line bisector (const Point &p) const; - inline double distance_to_point (const Point &p) const; /* distance to point! */ - inline double squared_distance_to_point (const Point &p) const; /* square of distance to point! */ - - inline bool is_finite (void) const; - inline const Point lerp (const double &a, const Point &p) const; -}; - -struct Vector { - inline Vector (double dx_, double dy_) : dx (dx_), dy (dy_) {} - inline explicit Vector (const Point &p) : dx (p.x), dy (p.y) {} - - inline bool operator == (const Vector &v) const; - inline bool operator != (const Vector &v) const; - inline const Vector operator+ (void) const; - inline const Vector operator- (void) const; - inline Vector& operator+= (const Vector &v); - inline Vector& operator-= (const Vector &v); - inline Vector& operator*= (const double &s); - inline Vector& operator/= (const double &s); - inline const Vector operator+ (const Vector &v) const; - inline const Vector operator- (const Vector &v) const; - inline const Vector operator* (const double &s) const; - inline const Vector operator/ (const double &s) const; - inline double operator* (const Vector &v) const; /* dot product */ - inline const Point operator+ (const Point &p) const; - - inline bool is_nonzero (void) const; - inline double len (void) const; - inline double len2 (void) const; - inline const Vector normalized (void) const; - inline const Vector ortho (void) const; - inline const Vector normal (void) const; /* ortho().normalized() */ - inline double angle (void) const; - - inline double cross (const Vector &other) const; - inline const Vector rebase (const Vector &bx, const Vector &by) const; - inline const Vector rebase (const Vector &bx) const; - - double dx, dy; -}; - -struct SignedVector : Vector { - inline SignedVector (const Vector &v, bool negative_) : Vector (v), negative (negative_) {} - - inline bool operator == (const SignedVector &v) const; - inline bool operator != (const SignedVector &v) const; - inline const SignedVector operator- (void) const; - - bool negative; -}; - -struct Line { - inline Line (double a_, double b_, double c_) : n (a_, b_), c (c_) {} - inline Line (Vector n_, double c_) : n (n_), c (c_) {} - inline Line (const Point &p0, const Point &p1) : - n ((p1 - p0).ortho ()), c (n * Vector (p0)) {} - - inline const Point operator+ (const Line &l) const; /* line intersection! */ - inline const SignedVector operator- (const Point &p) const; /* shortest vector from point to line */ - - - inline const Line normalized (void) const; - inline const Vector normal (void) const; - - Vector n; /* line normal */ - double c; /* n.dx*x + n.dy*y = c */ -}; - -struct Segment { - inline Segment (const Point &p0_, const Point &p1_) : - p0 (p0_), p1 (p1_) {} - - inline const SignedVector operator- (const Point &p) const; /* shortest vector from point to ***line*** */ - inline double distance_to_point (const Point &p) const; /* shortest distance from point to segment */ - inline double squared_distance_to_point (const Point &p) const; /* shortest distance squared from point to segment */ - inline bool contains_in_span (const Point &p) const; /* is p in the stripe formed by sliding this segment? */ - inline double max_distance_to_arc (const Arc &a) const; - - - Point p0; - Point p1; -}; - - - -struct Arc { - inline Arc (const Point &p0_, const Point &p1_, const Point &pm, bool complement) : - p0 (p0_), p1 (p1_), - d (p0_ == pm || p1_ == pm ? 0 : - tan (((p1_-pm).angle () - (p0_-pm).angle ()) / 2 - (complement ? 0 : M_PI_2))) {} - inline Arc (const Point &p0_, const Point &p1_, const double &d_) : - p0 (p0_), p1 (p1_), d (d_) {} - inline Arc (const Point ¢er, double radius, const double &a0, const double &a1, bool complement) : - p0 (center + Vector (cos(a0),sin(a0)) * radius), - p1 (center + Vector (cos(a1),sin(a1)) * radius), - d (tan ((a1 - a0) / 4 - (complement ? 0 : M_PI_2))) {} - inline Arc (const glyphy_arc_t &a) : p0 (a.p0), p1 (a.p1), d (a.d) {} - inline operator glyphy_arc_t (void) const { glyphy_arc_t a = {p0, p1, d}; return a; } - - inline bool operator == (const Arc &a) const; - inline bool operator != (const Arc &a) const; - inline const SignedVector operator- (const Point &p) const; /* shortest vector from point to arc */ - - inline double radius (void) const; - inline const Point center (void) const; - inline const Pair tangents (void) const; - - inline Bezier approximate_bezier (double *error) const; - - inline bool wedge_contains_point (const Point &p) const; - inline double distance_to_point (const Point &p) const; - inline double squared_distance_to_point (const Point &p) const; - inline double extended_dist (const Point &p) const; - - inline void extents (glyphy_extents_t &extents) const; - - Point p0, p1; - double d; /* Depth */ -}; - -struct Bezier { - inline Bezier (const Point &p0_, const Point &p1_, - const Point &p2_, const Point &p3_) : - p0 (p0_), p1 (p1_), p2 (p2_), p3 (p3_) {} - - inline const Point point (const double &t) const; - inline const Point midpoint (void) const; - inline const Vector tangent (const double &t) const; - inline const Vector d_tangent (const double &t) const; - inline double curvature (const double &t) const; - inline const Pair split (const double &t) const; - inline const Pair halve (void) const; - inline const Bezier segment (const double &t0, const double &t1) const; - - Point p0, p1, p2, p3; -}; - - -/* Implementations */ - - -/* Point */ - -inline Point::Point (const Vector &v) { - x = v.dx; - y = v.dy; -} -inline bool Point::operator == (const Point &p) const { - return x == p.x && y == p.y; -} -inline bool Point::operator != (const Point &p) const { - return !(*this == p); -} -inline Point& Point::operator+= (const Vector &v) { - x += v.dx; - y += v.dy; - return *this; -} -inline Point& Point::operator-= (const Vector &v) { - x -= v.dx; - y -= v.dy; - return *this; -} -inline const Point Point::operator+ (const Vector &v) const { - return Point (*this) += v; -} -inline const Point Point::operator- (const Vector &v) const { - return Point (*this) -= v; -} -inline const Vector Point::operator- (const Point &p) const { - return Vector (x - p.x, y - p.y); -} - -inline const Point Point::midpoint (const Point &p) const { - return *this + (p - *this) / 2; -} -inline const Line Point::bisector (const Point &p) const { - Vector d = p - *this; - return Line (d.dx * 2, d.dy * 2, d * Vector (p) + d * Vector (*this)); -} - -inline double Point::distance_to_point (const Point &p) const { - return ((*this) - p).len (); -} - -inline double Point::squared_distance_to_point (const Point &p) const { - return ((*this) - p).len2 (); -} - -inline bool Point::is_finite (void) const { - return isfinite (x) && isfinite (y); -} -inline const Point Point::lerp (const double &a, const Point &p) const { - /* The following two cases are special-cased to get better floating - * point stability. We require that points that are the same be - * bit-equal. */ - if (a == 0) return *this; - if (a == 1.0) return p; - return Point ((1-a) * x + a * p.x, (1-a) * y + a * p.y); -} - - -/* Vector */ - -inline bool Vector::operator == (const Vector &v) const { - return dx == v.dx && dy == v.dy; -} -inline bool Vector::operator != (const Vector &v) const { - return !(*this == v); -} -inline const Vector Vector::operator+ (void) const { - return *this; -} -inline const Vector Vector::operator- (void) const { - return Vector (-dx, -dy); -} -inline Vector& Vector::operator+= (const Vector &v) { - dx += v.dx; - dy += v.dy; - return *this; -} -inline Vector& Vector::operator-= (const Vector &v) { - dx -= v.dx; - dy -= v.dy; - return *this; -} -inline Vector& Vector::operator*= (const double &s) { - dx *= s; - dy *= s; - return *this; -} -inline Vector& Vector::operator/= (const double &s) { - dx /= s; - dy /= s; - return *this; -} -inline const Vector Vector::operator+ (const Vector &v) const { - return Vector (*this) += v; -} -inline const Vector Vector::operator- (const Vector &v) const { - return Vector (*this) -= v; -} -inline const Vector Vector::operator* (const double &s) const { - return Vector (*this) *= s; -} -inline const Vector operator* (const double &s, const Vector &v) { - return v * s; -} -inline const Vector Vector::operator/ (const double &s) const { - return Vector (*this) /= s; -} -inline double Vector::operator* (const Vector &v) const { /* dot product */ - return dx * v.dx + dy * v.dy; -} -inline const Point Vector::operator+ (const Point &p) const { - return p + *this; -} - -inline bool Vector::is_nonzero (void) const { - return dx || dy; -} -inline double Vector::len (void) const { - return hypot (dx, dy); -} -inline double Vector::len2 (void) const { - return dx * dx + dy * dy; -} -inline const Vector Vector::normalized (void) const { - double d = len (); - return d ? *this / d : *this; -} -inline const Vector Vector::ortho (void) const { - return Vector (-dy, dx); -} -inline const Vector Vector::normal (void) const { - return ortho ().normalized (); -} -inline double Vector::angle (void) const { - return atan2 (dy, dx); -} - -inline double Vector::cross (const Vector &other) const { - return dx * other.dy - dy * other.dx; -} -inline const Vector Vector::rebase (const Vector &bx, - const Vector &by) const { - return Vector (*this * bx, *this * by); -} -inline const Vector Vector::rebase (const Vector &bx) const { - return rebase (bx, bx.ortho ()); -} - - -/* SignedVector */ - -inline bool SignedVector::operator == (const SignedVector &v) const { - return (const Vector &)(*this) == (const Vector &)(v) && negative == v.negative; -} -inline bool SignedVector::operator != (const SignedVector &v) const { - return !(*this == v); -} -inline const SignedVector SignedVector::operator- (void) const { - return SignedVector (-(const Vector &)(*this), !negative); -} - - -/* Line */ - -inline const Point Line::operator+ (const Line &l) const { - double det = n.dx * l.n.dy - n.dy * l.n.dx; - if (!det) - return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); - return Point ((c * l.n.dy - n.dy * l.c) / det, - (n.dx * l.c - c * l.n.dx) / det); -} -inline const SignedVector Line::operator- (const Point &p) const { - double mag = -(n * Vector (p) - c) / n.len (); - return SignedVector (n.normalized () * mag, mag < 0); /******************************************************************************************* FIX. *************************************/ -} - -inline const SignedVector operator- (const Point &p, const Line &l) { - return -(l - p); -} - -inline const Line Line::normalized (void) const { - double d = n.len (); - return d ? Line (n / d, c / d) : *this; -} -inline const Vector Line::normal (void) const { - return n; -} - -/* Segment */ -inline const SignedVector Segment::operator- (const Point &p) const { - /* shortest vector from point to line */ - return p - Line (p1, p0); /************************************************************************************************** Should the order (p1, p0) depend on d?? ***********************/ -} - -/* Segment */ -inline bool Segment::contains_in_span (const Point &p) const { - if (p0 == p1) - return false; - - /* shortest vector from point to line */ - Line temp (p0, p1); - double mag = -(temp.n * Vector (p) - temp.c) / temp.n.len (); - Vector y (temp.n.normalized () * mag); - Point z = y + p; - - // Check if z is between p0 and p1. - - if (fabs (p1.y - p0.y) > fabs (p1.x - p0.x)) { - return ((z.y - p0.y > 0 && p1.y - p0.y > z.y - p0.y) || - (z.y - p0.y < 0 && p1.y - p0.y < z.y - p0.y)); - } - else { - return ((0 < z.x - p0.x && z.x - p0.x < p1.x - p0.x) || - (0 > z.x - p0.x && z.x - p0.x > p1.x - p0.x)); - } -} - -inline double Segment::distance_to_point (const Point &p) const { - if (p0 == p1) - return 0; - - // Check if z is between p0 and p1. - Line temp (p0, p1); - if (contains_in_span (p)) - return -(temp.n * Vector (p) - temp.c) / temp.n.len (); - - double dist_p_p0 = p.distance_to_point (p0); - double dist_p_p1 = p.distance_to_point (p1); - return (dist_p_p0 < dist_p_p1 ? dist_p_p0 : dist_p_p1) * (-(temp.n * Vector (p) - temp.c) < 0 ? -1 : 1); -} - - -inline double Segment::squared_distance_to_point (const Point &p) const { - if (p0 == p1) - return 0; - - // Check if z is between p0 and p1. - Line temp (p0, p1); - if (contains_in_span (p)) - return (temp.n * Vector (p) - temp.c) * (temp.n * Vector (p) - temp.c) / (temp.n * temp.n); - - double dist_p_p0 = p.squared_distance_to_point (p0); - double dist_p_p1 = p.squared_distance_to_point (p1); - return (dist_p_p0 < dist_p_p1 ? dist_p_p0 : dist_p_p1); -} - - -inline double Segment::max_distance_to_arc (const Arc &a) const { - double max_distance = fabs(a.distance_to_point(p0)) ; - return max_distance > fabs(a.distance_to_point(p1)) ? max_distance : fabs(a.distance_to_point(p1)) ; -} - - - -/* Arc */ - -inline bool Arc::operator == (const Arc &a) const { - return p0 == a.p0 && p1 == a.p1 && d == a.d; -} -inline bool Arc::operator != (const Arc &a) const { - return !(*this == a); -} - - -inline const SignedVector Arc::operator- (const Point &p) const { - - if (fabs(d) < 1e-5) { - Segment arc_segment (p0, p1); - return arc_segment - p; - } - if (wedge_contains_point (p)){ - Vector difference = (center () - p).normalized () * fabs (p.distance_to_point (center ()) - radius ()); - - return SignedVector (difference, ((p - center ()).len () < radius ()) ^ (d < 0)); - } - double d0 = p.squared_distance_to_point (p0); - double d1 = p.squared_distance_to_point (p1); - - Arc other_arc (p0, p1, (1.0 + d) / (1.0 - d)); /********************************* NOT Robust. But works? *****************/ - Vector normal = center () - (d0 < d1 ? p0 : p1) ; - - if (normal.len() == 0) - return SignedVector (Vector (0, 0), true); /************************************ Check sign of this S.D. *************/ - - return SignedVector (Line (normal.dx, normal.dy, normal * Vector ((d0 < d1 ? p0 : p1))) - p, !other_arc.wedge_contains_point(p)); -} - -inline const SignedVector operator- (const Point &p, const Arc &a) { - return -(a - p); -} - - - -inline double Arc::radius (void) const -{ - return fabs ((p1 - p0).len () / (2 * sin2atan (d))); -} - -inline const Point Arc::center (void) const -{ - return (p0.midpoint (p1)) + (p1 - p0).ortho () / (2 * tan2atan (d)); -} - -inline const Pair Arc::tangents (void) const -{ - Vector dp = (p1 - p0) * .5; - Vector pp = dp.ortho () * -sin2atan (d); - dp = dp * cos2atan (d); - return Pair (dp + pp, dp - pp); -} - - - -inline Bezier Arc::approximate_bezier (double *error) const -{ - Vector dp = p1 - p0; - Vector pp = dp.ortho (); - - if (error) - *error = dp.len () * pow (fabs (d), 5) / (54 * (1 + d*d)); - - dp *= ((1 - d*d) / 3); - pp *= (2 * d / 3); - - Point p0s = p0 + dp - pp; - Point p1s = p1 - dp - pp; - - return Bezier (p0, p0s, p1s, p1); -} - - -inline bool Arc::wedge_contains_point (const Point &p) const -{ - Pair t = tangents (); - if (fabs (d) <= 1) - return (p - p0) * t.first >= 0 && (p - p1) * t.second <= 0; - else - return (p - p0) * t.first >= 0 || (p - p1) * t.second <= 0; -} - - -/* Distance may not always be positive, but will be to an endpoint whenever necessary. */ -inline double Arc::distance_to_point (const Point &p) const { - if (fabs(d) < 1e-5) { - Segment arc_segment (p0, p1); - return arc_segment.distance_to_point (p); - } - - SignedVector difference = *this - p; - - if (wedge_contains_point (p) && fabs(d) > 1e-5) - return fabs (p.distance_to_point (center ()) - radius ()) * (difference.negative ? -1 : 1); - double d1 = p.squared_distance_to_point (p0); - double d2 = p.squared_distance_to_point (p1); - return (d1 < d2 ? sqrt(d1) : sqrt(d2)) * (difference.negative ? -1 : 1); -} - -/* Distance will be to an endpoint whenever necessary. */ -inline double Arc::squared_distance_to_point (const Point &p) const { - if (fabs(d) < 1e-5) { - Segment arc_segment (p0, p1); - return arc_segment.squared_distance_to_point (p); - } - - //SignedVector difference = *this - p; - - if (wedge_contains_point (p) && fabs(d) > 1e-5) { - double answer = p.distance_to_point (center ()) - radius (); - return answer * answer; - } - double d1 = p.squared_distance_to_point (p0); - double d2 = p.squared_distance_to_point (p1); - return (d1 < d2 ? d1 : d2); -} - -inline double Arc::extended_dist (const Point &p) const { - Point m = p0.lerp (.5, p1); - Vector dp = p1 - p0; - Vector pp = dp.ortho (); - float d2 = tan2atan (d); - if ((p - m) * (p1 - m) < 0) - return (p - p0) * (pp + dp * d2).normalized (); - else - return (p - p1) * (pp - dp * d2).normalized (); -} - -inline void Arc::extents (glyphy_extents_t &extents) const { - glyphy_extents_clear (&extents); - glyphy_extents_add (&extents, &p0); - glyphy_extents_add (&extents, &p1); - Point c = center (); - double r = radius (); - Point p[4] = {c + r * Vector (-1, 0), - c + r * Vector (+1, 0), - c + r * Vector ( 0, -1), - c + r * Vector ( 0, +1)}; - for (unsigned int i = 0; i < 4; i++) - if (wedge_contains_point (p[i])) - glyphy_extents_add (&extents, &p[i]); -} - - -/* Bezier */ - -inline const Point Bezier::point (const double &t) const { - Point p01 = p0.lerp (t, p1); - Point p12 = p1.lerp (t, p2); - Point p23 = p2.lerp (t, p3); - Point p012 = p01.lerp (t, p12); - Point p123 = p12.lerp (t, p23); - Point p0123 = p012.lerp (t, p123); - return p0123; -} - -inline const Point Bezier::midpoint (void) const -{ - Point p01 = p0.midpoint (p1); - Point p12 = p1.midpoint (p2); - Point p23 = p2.midpoint (p3); - Point p012 = p01.midpoint (p12); - Point p123 = p12.midpoint (p23); - Point p0123 = p012.midpoint (p123); - return p0123; -} - -inline const Vector Bezier::tangent (const double &t) const -{ - double t_2_0 = t * t; - double t_0_2 = (1 - t) * (1 - t); - - double _1__4t_1_0_3t_2_0 = 1 - 4 * t + 3 * t_2_0; - double _2t_1_0_3t_2_0 = 2 * t - 3 * t_2_0; - - return Vector (-3 * p0.x * t_0_2 - +3 * p1.x * _1__4t_1_0_3t_2_0 - +3 * p2.x * _2t_1_0_3t_2_0 - +3 * p3.x * t_2_0, - -3 * p0.y * t_0_2 - +3 * p1.y * _1__4t_1_0_3t_2_0 - +3 * p2.y * _2t_1_0_3t_2_0 - +3 * p3.y * t_2_0); -} - -inline const Vector Bezier::d_tangent (const double &t) const { - return Vector (6 * ((-p0.x + 3*p1.x - 3*p2.x + p3.x) * t + (p0.x - 2*p1.x + p2.x)), - 6 * ((-p0.y + 3*p1.y - 3*p2.y + p3.y) * t + (p0.y - 2*p1.y + p2.y))); -} - -inline double Bezier::curvature (const double &t) const { - Vector dpp = tangent (t).ortho (); - Vector ddp = d_tangent (t); - /* normal vector len squared */ - double len = dpp.len (); - double curvature = (dpp * ddp) / (len * len * len); - return curvature; -} - -inline const Pair Bezier::split (const double &t) const { - Point p01 = p0.lerp (t, p1); - Point p12 = p1.lerp (t, p2); - Point p23 = p2.lerp (t, p3); - Point p012 = p01.lerp (t, p12); - Point p123 = p12.lerp (t, p23); - Point p0123 = p012.lerp (t, p123); - return Pair (Bezier (p0, p01, p012, p0123), - Bezier (p0123, p123, p23, p3)); -} - -inline const Pair Bezier::halve (void) const -{ - Point p01 = p0.midpoint (p1); - Point p12 = p1.midpoint (p2); - Point p23 = p2.midpoint (p3); - Point p012 = p01.midpoint (p12); - Point p123 = p12.midpoint (p23); - Point p0123 = p012.midpoint (p123); - return Pair (Bezier (p0, p01, p012, p0123), - Bezier (p0123, p123, p23, p3)); -} - -inline const Bezier Bezier::segment (const double &t0, const double &t1) const -{ - Point p01 = p0.lerp (t0, p1); - Point p12 = p1.lerp (t0, p2); - Point p23 = p2.lerp (t0, p3); - Point p012 = p01.lerp (t0, p12); - Point p123 = p12.lerp (t0, p23); - Point p0123 = p012.lerp (t0, p123); - - Point q01 = p0.lerp (t1, p1); - Point q12 = p1.lerp (t1, p2); - Point q23 = p2.lerp (t1, p3); - Point q012 = q01.lerp (t1, q12); - Point q123 = q12.lerp (t1, q23); - Point q0123 = q012.lerp (t1, q123); - - return Bezier (p0123, - p0123 + (p123 - p0123) * ((t1 - t0) / (1 - t0)), - q0123 + (q012 - q0123) * ((t1 - t0) / t1), - q0123); -} - - -/* insertion operator */ - - -static inline std::ostream& operator<<(std::ostream& os, const Point& p) -{ - os << "Point(" << p.x << "," << p.y << ")"; - return os; -} -static inline std::ostream& operator<<(std::ostream& os, const Vector& v) -{ - os << "Vector(" << v.dx << "," << v.dy << ")"; - return os; -} -static inline std::ostream& operator<<(std::ostream& os, const Arc& a) -{ - os << "Arc(" << a.p0 << ", " << a.p1 << ", " << a.d << ")"; - return os; -} -static inline std::ostream& operator<<(std::ostream& os, const Bezier& b) -{ - os << "Bezier(" << b.p0 << ", " << b.p1 << ", " << b.p2 << ", " << b.p3 << ")"; - return os; -} - -} /* namespace Geometry */ -} /* namespace GLyphy */ - -#endif /* GLYPHY_GEOMETRY_HH */ diff --git a/src/glyphy-outline.cc b/src/glyphy-outline.cc deleted file mode 100644 index 7fded28..0000000 --- a/src/glyphy-outline.cc +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" - -using namespace GLyphy::Geometry; - - -void -glyphy_outline_reverse (glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints) -{ - if (!num_endpoints) - return; - - // Shift the d's first - double d0 = endpoints[0].d; - for (unsigned int i = 0; i < num_endpoints - 1; i++) - endpoints[i].d = endpoints[i + 1].d == GLYPHY_INFINITY ? GLYPHY_INFINITY : -endpoints[i + 1].d; - endpoints[num_endpoints - 1].d = d0; - - // Reverse - for (unsigned int i = 0, j = num_endpoints - 1; i < j; i++, j--) { - glyphy_arc_endpoint_t t = endpoints[i]; - endpoints[i] = endpoints[j]; - endpoints[j] = t; - } -} - - -static bool -winding (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints) -{ - /* - * Algorithm: - * - * - Approximate arcs with triangles passing through the mid- and end-points, - * - Calculate the area of the contour, - * - Return sign. - */ - - double area = 0; - for (unsigned int i = 1; i < num_endpoints; i++) - { - const glyphy_point_t &p0 = endpoints[i - 1].p; - const glyphy_point_t &p1 = endpoints[i].p; - double d = endpoints[i].d; - - assert (d != GLYPHY_INFINITY); - - area += Vector(p0).cross (Vector(p1)); - area -= .5 * d * (Point(p1) - Point(p0)).len2 (); - } - return area < 0; -} - - -static int -categorize (double v, double ref) -{ - return v < ref - GLYPHY_EPSILON ? -1 : v > ref + GLYPHY_EPSILON ? +1 : 0; -} - -static bool -is_zero (double v) -{ - return fabs (v) < GLYPHY_EPSILON; -} - -static bool -even_odd (const glyphy_arc_endpoint_t *c_endpoints, - unsigned int num_c_endpoints, - const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints) -{ - /* - * Algorithm: - * - * - For a point on the contour, draw a halfline in a direction - * (eg. decreasing x) to infinity, - * - Count how many times it crosses all other contours, - * - Pay special attention to points falling exactly on the halfline, - * specifically, they count as +.5 or -.5, depending the direction - * of crossing. - * - * All this counting is extremely tricky: - * - * - Floating point equality cannot be relied on here, - * - Lots of arc analysis needed, - * - Without having a point that we know falls /inside/ the contour, - * there are legitimate cases that we simply cannot handle using - * this algorithm. For example, imagine the following glyph shape: - * - * +---------+ - * | +-----+ | - * | \ / | - * | \ / | - * +----o----+ - * - * If the glyph is defined as two outlines, and when analysing the - * inner outline we happen to pick the point denoted by 'o' for - * analysis, there simply is no way to differentiate this case from - * the following case: - * - * +---------+ - * | | - * | | - * | | - * +----o----+ - * / \ - * / \ - * +-----+ - * - * However, in one, the triangle should be filled in, and in the other - * filled out. - * - * One way to work around this may be to do the analysis for all endpoints - * on the outline and take majority. But even that can fail in more - * extreme yet legitimate cases, such as this one: - * - * +--+--+ - * | / \ | - * |/ \| - * + + - * |\ /| - * | \ / | - * +--o--+ - * - * The only correct algorithm I can think of requires a point that falls - * fully inside the outline. While we can try finding such a point (not - * dissimilar to the winding algorithm), it's beyond what I'm willing to - * implement right now. - */ - - const Point p = c_endpoints[0].p; - - double count = 0; - Point p0 (0, 0); - for (unsigned int i = 0; i < num_endpoints; i++) { - const glyphy_arc_endpoint_t &endpoint = endpoints[i]; - if (endpoint.d == GLYPHY_INFINITY) { - p0 = endpoint.p; - continue; - } - Arc arc (p0, endpoint.p, endpoint.d); - p0 = endpoint.p; - - /* - * Skip our own contour - */ - if (&endpoint >= c_endpoints && &endpoint < c_endpoints + num_c_endpoints) - continue; - - /* End-point y's compared to the ref point; lt, eq, or gt */ - unsigned s0 = categorize (arc.p0.y, p.y); - unsigned s1 = categorize (arc.p1.y, p.y); - - if (is_zero (arc.d)) - { - /* Line */ - - if (!s0 || !s1) - { - /* - * Add +.5 / -.5 for each endpoint on the halfline, depending on - * crossing direction. - */ - Pair t = arc.tangents (); - if (!s0 && arc.p0.x < p.x + GLYPHY_EPSILON) - count += .5 * categorize (t.first.dy, 0); - if (!s1 && arc.p1.x < p.x + GLYPHY_EPSILON) - count += .5 * categorize (t.second.dy, 0); - continue; - } - - if (s0 == s1) - continue; // Segment fully above or below the halfline - - // Find x pos that the line segment would intersect the half-line. - double x = arc.p0.x + (arc.p1.x - arc.p0.x) * ((p.y - arc.p0.y) / (arc.p1.y - arc.p0.y)); - - if (x >= p.x - GLYPHY_EPSILON) - continue; // Does not intersect halfline - - count++; // Add one for full crossing - continue; - } - else - { - /* Arc */ - - if (!s0 || !s1) - { - /* - * Add +.5 / -.5 for each endpoint on the halfline, depending on - * crossing direction. - */ - Pair t = arc.tangents (); - - /* Arc-specific logic: - * If the tangent has dy==0, use the other endpoint's - * y value to decide which way the arc will be heading. - */ - if (is_zero (t.first.dy)) - t.first.dy = +categorize (arc.p1.y, p.y); - if (is_zero (t.second.dy)) - t.second.dy = -categorize (arc.p0.y, p.y); - - if (!s0 && arc.p0.x < p.x + GLYPHY_EPSILON) - count += .5 * categorize (t.first.dy, 0); - if (!s1 && arc.p1.x < p.x + GLYPHY_EPSILON) - count += .5 * categorize (t.second.dy, 0); - } - - Point c = arc.center (); - double r = arc.radius (); - if (c.x - r >= p.x) - continue; // No chance - /* Solve for arc crossing line with y = p.y */ - double dy = p.y - c.y; - double x2 = r * r - dy * dy; - if (x2 <= GLYPHY_EPSILON) - continue; // Negative delta, no crossing - double dx = sqrt (x2); - /* There's two candidate points on the arc with the same y as the - * ref point. */ - Point pp[2] = { Point (c.x - dx, p.y), - Point (c.x + dx, p.y) }; - -#define POINTS_EQ(a,b) (is_zero (a.x - b.x) && is_zero (a.y - b.y)) - for (unsigned int i = 0; i < ARRAY_LENGTH (pp); i++) - { - /* Make sure we don't double-count endpoints that fall on the - * halfline as we already accounted for those above */ - if (!POINTS_EQ (pp[i], arc.p0) && !POINTS_EQ (pp[i], arc.p1) && - pp[i].x < p.x - GLYPHY_EPSILON && arc.wedge_contains_point (pp[i])) - count++; // Add one for full crossing - } -#undef POINTS_EQ - } - } - - return !(int (floor (count)) & 1); -} - -static bool -process_contour (glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - const glyphy_arc_endpoint_t *all_endpoints, - unsigned int num_all_endpoints, - bool inverse) -{ - /* - * Algorithm: - * - * - Find the winding direction and even-odd number, - * - If the two disagree, reverse the contour, inplace. - */ - - if (!num_endpoints) - return false; - - if (num_endpoints < 3) { - abort (); // Don't expect this - return false; // Need at least two arcs - } - if (Point (endpoints[0].p) != Point (endpoints[num_endpoints-1].p)) { - abort (); // Don't expect this - return false; // Need a closed contour - } - - if (inverse ^ - winding (endpoints, num_endpoints) ^ - even_odd (endpoints, num_endpoints, all_endpoints, num_all_endpoints)) - { - glyphy_outline_reverse (endpoints, num_endpoints); - return true; - } - - return false; -} - -/* Returns true if outline was modified */ -glyphy_bool_t -glyphy_outline_winding_from_even_odd (glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - glyphy_bool_t inverse) -{ - /* - * Algorithm: - * - * - Process one contour at a time. - */ - - unsigned int start = 0; - bool ret = false; - for (unsigned int i = 1; i < num_endpoints; i++) { - const glyphy_arc_endpoint_t &endpoint = endpoints[i]; - if (endpoint.d == GLYPHY_INFINITY) { - ret = ret | process_contour (endpoints + start, i - start, endpoints, num_endpoints, bool (inverse)); - start = i; - } - } - ret = ret | process_contour (endpoints + start, num_endpoints - start, endpoints, num_endpoints, bool (inverse)); - return ret; -} diff --git a/src/glyphy-sdf-glsl.h b/src/glyphy-sdf-glsl.h deleted file mode 100644 index a86590d..0000000 --- a/src/glyphy-sdf-glsl.h +++ /dev/null @@ -1,155 +0,0 @@ -static const char *glyphy_sdf_glsl = -"/*\n" -" * Copyright 2012 Google, Inc. All Rights Reserved.\n" -" *\n" -" * Licensed under the Apache License, Version 2.0 (the \"License\");\n" -" * you may not use this file except in compliance with the License.\n" -" * You may obtain a copy of the License at\n" -" *\n" -" * http://www.apache.org/licenses/LICENSE-2.0\n" -" *\n" -" * Unless required by applicable law or agreed to in writing, software\n" -" * distributed under the License is distributed on an \"AS IS\" BASIS,\n" -" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" -" * See the License for the specific language governing permissions and\n" -" * limitations under the License.\n" -" *\n" -" * Google Author(s): Behdad Esfahbod, Maysum Panju\n" -" */\n" -"\n" -"#ifndef GLYPHY_TEXTURE1D_FUNC\n" -"#define GLYPHY_TEXTURE1D_FUNC glyphy_texture1D_func\n" -"#endif\n" -"#ifndef GLYPHY_TEXTURE1D_EXTRA_DECLS\n" -"#define GLYPHY_TEXTURE1D_EXTRA_DECLS\n" -"#endif\n" -"#ifndef GLYPHY_TEXTURE1D_EXTRA_ARGS\n" -"#define GLYPHY_TEXTURE1D_EXTRA_ARGS\n" -"#endif\n" -"\n" -"#ifndef GLYPHY_SDF_TEXTURE1D_FUNC\n" -"#define GLYPHY_SDF_TEXTURE1D_FUNC GLYPHY_TEXTURE1D_FUNC\n" -"#endif\n" -"#ifndef GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS\n" -"#define GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS GLYPHY_TEXTURE1D_EXTRA_DECLS\n" -"#endif\n" -"#ifndef GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS\n" -"#define GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS GLYPHY_TEXTURE1D_EXTRA_ARGS\n" -"#endif\n" -"#ifndef GLYPHY_SDF_TEXTURE1D\n" -"#define GLYPHY_SDF_TEXTURE1D(offset) GLYPHY_RGBA(GLYPHY_SDF_TEXTURE1D_FUNC (offset GLYPHY_TEXTURE1D_EXTRA_ARGS))\n" -"#endif\n" -"\n" -"#ifndef GLYPHY_MAX_NUM_ENDPOINTS\n" -"#define GLYPHY_MAX_NUM_ENDPOINTS 32\n" -"#endif\n" -"\n" -"glyphy_arc_list_t\n" -"glyphy_arc_list (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS)\n" -"{\n" -" int cell_offset = glyphy_arc_list_offset (p, nominal_size);\n" -" vec4 arc_list_data = GLYPHY_SDF_TEXTURE1D (cell_offset);\n" -" return glyphy_arc_list_decode (arc_list_data, nominal_size);\n" -"}\n" -"\n" -"float\n" -"glyphy_sdf (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS)\n" -"{\n" -" glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS);\n" -"\n" -" /* Short-circuits */\n" -" if (arc_list.num_endpoints == 0) {\n" -" /* far-away cell */\n" -" return GLYPHY_INFINITY * float(arc_list.side);\n" -" } if (arc_list.num_endpoints == -1) {\n" -" /* single-line */\n" -" float angle = arc_list.line_angle;\n" -" vec2 n = vec2 (cos (angle), sin (angle));\n" -" return dot (p - (vec2(nominal_size) * .5), n) - arc_list.line_distance;\n" -" }\n" -"\n" -" float side = float(arc_list.side);\n" -" float min_dist = GLYPHY_INFINITY;\n" -" glyphy_arc_t closest_arc;\n" -"\n" -" glyphy_arc_endpoint_t endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset), nominal_size);\n" -" vec2 pp = endpoint.p;\n" -" for (int i = 1; i < GLYPHY_MAX_NUM_ENDPOINTS; i++)\n" -" {\n" -" if (i >= arc_list.num_endpoints) {\n" -" break;\n" -" }\n" -" endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset + i), nominal_size);\n" -" glyphy_arc_t a = glyphy_arc_t (pp, endpoint.p, endpoint.d);\n" -" if (glyphy_isinf (a.d)) {\n" -" pp = endpoint.p;\n" -" continue;\n" -" }\n" -" \n" -" if (glyphy_arc_wedge_contains (a, p))\n" -" {\n" -" float sdist = glyphy_arc_wedge_signed_dist (a, p);\n" -" float udist = abs (sdist) * (1. - GLYPHY_EPSILON);\n" -" if (udist <= min_dist) {\n" -" min_dist = udist;\n" -" side = sdist <= 0. ? -1. : +1.;\n" -" }\n" -" } else {\n" -" float udist = min (distance (p, a.p0), distance (p, a.p1));\n" -" if (udist < min_dist - GLYPHY_EPSILON) {\n" -" min_dist = udist;\n" -" side = 0.; /* unsure */\n" -" closest_arc = a;\n" -" } else if (side == 0. && udist - min_dist <= GLYPHY_EPSILON) {\n" -" /* If this new distance is the same as the current minimum,\n" -" * compare extended distances. Take the sign from the arc\n" -" * with larger extended distance. */\n" -" float old_ext_dist = glyphy_arc_extended_dist (closest_arc, p);\n" -" float new_ext_dist = glyphy_arc_extended_dist (a, p);\n" -"\n" -" float ext_dist = abs (new_ext_dist) <= abs (old_ext_dist) ?\n" -" old_ext_dist : new_ext_dist;\n" -"\n" -"#ifdef GLYPHY_SDF_PSEUDO_DISTANCE\n" -" /* For emboldening and stuff: */\n" -" min_dist = abs (ext_dist);\n" -"#endif\n" -" side = sign (ext_dist);\n" -" }\n" -" }\n" -" pp = endpoint.p;\n" -" }\n" -"\n" -" if (side == 0.) {\n" -" // Technically speaking this should not happen, but it does. So try to fix it.\n" -" float ext_dist = glyphy_arc_extended_dist (closest_arc, p);\n" -" side = sign (ext_dist);\n" -" }\n" -"\n" -" return min_dist * side;\n" -"}\n" -"\n" -"float\n" -"glyphy_point_dist (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS)\n" -"{\n" -" glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS);\n" -"\n" -" float side = float(arc_list.side);\n" -" float min_dist = GLYPHY_INFINITY;\n" -"\n" -" if (arc_list.num_endpoints == 0)\n" -" return min_dist;\n" -"\n" -" glyphy_arc_endpoint_t endpoint;\n" -" for (int i = 0; i < GLYPHY_MAX_NUM_ENDPOINTS; i++)\n" -" {\n" -" if (i >= arc_list.num_endpoints) {\n" -" break;\n" -" }\n" -" endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset + i), nominal_size);\n" -" if (glyphy_isinf (endpoint.d)) continue;\n" -" min_dist = min (min_dist, distance (p, endpoint.p));\n" -" }\n" -" return min_dist;\n" -"}\n" -; diff --git a/src/glyphy-sdf.cc b/src/glyphy-sdf.cc deleted file mode 100644 index 7cdc31c..0000000 --- a/src/glyphy-sdf.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "glyphy-common.hh" -#include "glyphy-geometry.hh" - -using namespace GLyphy::Geometry; - -/* - * TODO - * - * Sync this with the shader sdf - */ - -double -glyphy_sdf_from_arc_list (const glyphy_arc_endpoint_t *endpoints, - unsigned int num_endpoints, - const glyphy_point_t *p, - glyphy_point_t *closest_p /* may be NULL; TBD not implemented yet */) -{ - Point c = *p; - Point p0 (0, 0); - Arc closest_arc (p0, p0, 0); - double min_dist = GLYPHY_INFINITY; - int side = 0; - for (unsigned int i = 0; i < num_endpoints; i++) { - const glyphy_arc_endpoint_t &endpoint = endpoints[i]; - if (endpoint.d == GLYPHY_INFINITY) { - p0 = endpoint.p; - continue; - } - Arc arc (p0, endpoint.p, endpoint.d); - p0 = endpoint.p; - - if (arc.wedge_contains_point (c)) { - double sdist = arc.distance_to_point (c); /* TODO This distance has the wrong sign. Fix */ - double udist = fabs (sdist) * (1 - GLYPHY_EPSILON); - if (udist <= min_dist) { - min_dist = udist; - side = sdist >= 0 ? -1 : +1; - } - } else { - double udist = std::min ((arc.p0 - c).len (), (arc.p1 - c).len ()); - if (udist < min_dist) { - min_dist = udist; - side = 0; /* unsure */ - closest_arc = arc; - } else if (side == 0 && udist == min_dist) { - /* If this new distance is the same as the current minimum, - * compare extended distances. Take the sign from the arc - * with larger extended distance. */ - double old_ext_dist = closest_arc.extended_dist (c); - double new_ext_dist = arc.extended_dist (c); - - double ext_dist = fabs (new_ext_dist) <= fabs (old_ext_dist) ? - old_ext_dist : new_ext_dist; - - /* For emboldening and stuff: */ - // min_dist = fabs (ext_dist); - side = ext_dist >= 0 ? +1 : -1; - } - } - } - - if (side == 0) { - // Technically speaking this should not happen, but it does. So try to fix it. - double ext_dist = closest_arc.extended_dist (c); - side = ext_dist >= 0 ? +1 : -1; - } - - return side * min_dist; -} diff --git a/src/glyphy-sdf.glsl b/src/glyphy-sdf.glsl deleted file mode 100644 index daad010..0000000 --- a/src/glyphy-sdf.glsl +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2012 Google, Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju - */ - -#ifndef GLYPHY_TEXTURE1D_FUNC -#define GLYPHY_TEXTURE1D_FUNC glyphy_texture1D_func -#endif -#ifndef GLYPHY_TEXTURE1D_EXTRA_DECLS -#define GLYPHY_TEXTURE1D_EXTRA_DECLS -#endif -#ifndef GLYPHY_TEXTURE1D_EXTRA_ARGS -#define GLYPHY_TEXTURE1D_EXTRA_ARGS -#endif - -#ifndef GLYPHY_SDF_TEXTURE1D_FUNC -#define GLYPHY_SDF_TEXTURE1D_FUNC GLYPHY_TEXTURE1D_FUNC -#endif -#ifndef GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS -#define GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS GLYPHY_TEXTURE1D_EXTRA_DECLS -#endif -#ifndef GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS -#define GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS GLYPHY_TEXTURE1D_EXTRA_ARGS -#endif -#ifndef GLYPHY_SDF_TEXTURE1D -#define GLYPHY_SDF_TEXTURE1D(offset) GLYPHY_RGBA(GLYPHY_SDF_TEXTURE1D_FUNC (offset GLYPHY_TEXTURE1D_EXTRA_ARGS)) -#endif - -#ifndef GLYPHY_MAX_NUM_ENDPOINTS -#define GLYPHY_MAX_NUM_ENDPOINTS 32 -#endif - -glyphy_arc_list_t -glyphy_arc_list (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) -{ - int cell_offset = glyphy_arc_list_offset (p, nominal_size); - vec4 arc_list_data = GLYPHY_SDF_TEXTURE1D (cell_offset); - return glyphy_arc_list_decode (arc_list_data, nominal_size); -} - -float -glyphy_sdf (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) -{ - glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); - - /* Short-circuits */ - if (arc_list.num_endpoints == 0) { - /* far-away cell */ - return GLYPHY_INFINITY * float(arc_list.side); - } if (arc_list.num_endpoints == -1) { - /* single-line */ - float angle = arc_list.line_angle; - vec2 n = vec2 (cos (angle), sin (angle)); - return dot (p - (vec2(nominal_size) * .5), n) - arc_list.line_distance; - } - - float side = float(arc_list.side); - float min_dist = GLYPHY_INFINITY; - glyphy_arc_t closest_arc; - - glyphy_arc_endpoint_t endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset), nominal_size); - vec2 pp = endpoint.p; - for (int i = 1; i < GLYPHY_MAX_NUM_ENDPOINTS; i++) - { - if (i >= arc_list.num_endpoints) { - break; - } - endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset + i), nominal_size); - glyphy_arc_t a = glyphy_arc_t (pp, endpoint.p, endpoint.d); - if (glyphy_isinf (a.d)) { - pp = endpoint.p; - continue; - } - - if (glyphy_arc_wedge_contains (a, p)) - { - float sdist = glyphy_arc_wedge_signed_dist (a, p); - float udist = abs (sdist) * (1. - GLYPHY_EPSILON); - if (udist <= min_dist) { - min_dist = udist; - side = sdist <= 0. ? -1. : +1.; - } - } else { - float udist = min (distance (p, a.p0), distance (p, a.p1)); - if (udist < min_dist - GLYPHY_EPSILON) { - min_dist = udist; - side = 0.; /* unsure */ - closest_arc = a; - } else if (side == 0. && udist - min_dist <= GLYPHY_EPSILON) { - /* If this new distance is the same as the current minimum, - * compare extended distances. Take the sign from the arc - * with larger extended distance. */ - float old_ext_dist = glyphy_arc_extended_dist (closest_arc, p); - float new_ext_dist = glyphy_arc_extended_dist (a, p); - - float ext_dist = abs (new_ext_dist) <= abs (old_ext_dist) ? - old_ext_dist : new_ext_dist; - -#ifdef GLYPHY_SDF_PSEUDO_DISTANCE - /* For emboldening and stuff: */ - min_dist = abs (ext_dist); -#endif - side = sign (ext_dist); - } - } - pp = endpoint.p; - } - - if (side == 0.) { - // Technically speaking this should not happen, but it does. So try to fix it. - float ext_dist = glyphy_arc_extended_dist (closest_arc, p); - side = sign (ext_dist); - } - - return min_dist * side; -} - -float -glyphy_point_dist (const vec2 p, const ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) -{ - glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); - - float side = float(arc_list.side); - float min_dist = GLYPHY_INFINITY; - - if (arc_list.num_endpoints == 0) - return min_dist; - - glyphy_arc_endpoint_t endpoint; - for (int i = 0; i < GLYPHY_MAX_NUM_ENDPOINTS; i++) - { - if (i >= arc_list.num_endpoints) { - break; - } - endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset + i), nominal_size); - if (glyphy_isinf (endpoint.d)) continue; - min_dist = min (min_dist, distance (p, endpoint.p)); - } - return min_dist; -} From f9ab1b06e48819271336900c037d5f3503786001 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 22:07:15 -0600 Subject: [PATCH 09/68] Fix missing close_path and band boundary precision - Add close_path callback to HarfBuzz draw funcs. Without it, the last contour's closing segment was never emitted, causing winding number errors (horizontal streak artifacts). - Use floor() instead of (int) cast for band assignment to avoid off-by-one at band boundaries due to floating-point truncation. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 8 ++++---- src/glyphy-harfbuzz.h | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index aba17fb..ab31047 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -148,8 +148,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, if (height > 0) { double min_y, max_y; curve_y_range (&curves[i], &min_y, &max_y); - int band_lo = (int) ((min_y - extents->min_y) / hband_size); - int band_hi = (int) ((max_y - extents->min_y) / hband_size); + int band_lo = (int) floor ((min_y - extents->min_y) / hband_size); + int band_hi = (int) floor ((max_y - extents->min_y) / hband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_hbands - 1); for (int b = band_lo; b <= band_hi; b++) @@ -163,8 +163,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, if (width > 0) { double min_x, max_x; curve_x_range (&curves[i], &min_x, &max_x); - int band_lo = (int) ((min_x - extents->min_x) / vband_size); - int band_hi = (int) ((max_x - extents->min_x) / vband_size); + int band_lo = (int) floor ((min_x - extents->min_x) / vband_size); + int band_hi = (int) floor ((max_x - extents->min_x) / vband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_vbands - 1); for (int b = band_lo; b <= band_hi; b++) diff --git a/src/glyphy-harfbuzz.h b/src/glyphy-harfbuzz.h index 2b1cf5e..37d162d 100644 --- a/src/glyphy-harfbuzz.h +++ b/src/glyphy-harfbuzz.h @@ -83,6 +83,15 @@ glyphy_harfbuzz(cubic_to) (hb_draw_funcs_t *dfuncs, glyphy_curve_accumulator_line_to (acc, &p1); } +static void +glyphy_harfbuzz(close_path) (hb_draw_funcs_t *dfuncs, + glyphy_curve_accumulator_t *acc, + hb_draw_state_t *st, + void *user_data) +{ + glyphy_curve_accumulator_close_path (acc); +} + static hb_draw_funcs_t * glyphy_harfbuzz(get_draw_funcs) (void) { @@ -95,6 +104,7 @@ glyphy_harfbuzz(get_draw_funcs) (void) hb_draw_funcs_set_line_to_func (dfuncs, (hb_draw_line_to_func_t) glyphy_harfbuzz(line_to), NULL, NULL); hb_draw_funcs_set_quadratic_to_func (dfuncs, (hb_draw_quadratic_to_func_t) glyphy_harfbuzz(quadratic_to), NULL, NULL); hb_draw_funcs_set_cubic_to_func (dfuncs, (hb_draw_cubic_to_func_t) glyphy_harfbuzz(cubic_to), NULL, NULL); + hb_draw_funcs_set_close_path_func (dfuncs, (hb_draw_close_path_func_t) glyphy_harfbuzz(close_path), NULL, NULL); hb_draw_funcs_make_immutable (dfuncs); } From 11e61e249131ded4e87828ced1d308657219c70c Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 22:51:22 -0600 Subject: [PATCH 10/68] [meson] Adjust glut finding --- meson.build | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index fcc0320..6c00d1c 100644 --- a/meson.build +++ b/meson.build @@ -62,7 +62,19 @@ endif harfbuzz_dep = dependency('harfbuzz', version: '>= 4.0.0', required: true) gl_dep = dependency('gl', required: true) glew_dep = dependency('glew', required: false) -glut_dep = dependency('GLUT', required: get_option('demo').enabled()) +glut_dep = dependency('glut', method: 'pkg-config', required: false) + +if not glut_dep.found() + glut_dep = dependency('GLUT', method: 'pkg-config', required: false) +endif + +if not glut_dep.found() + glut_dep = dependency('GLUT', method: 'cmake', required: false) +endif + +if not glut_dep.found() + glut_dep = dependency('FreeGLUT', method: 'cmake', required: get_option('demo').enabled()) +endif prefix = get_option('prefix') libdir = join_paths(prefix, get_option('libdir')) From 9e87d563d95ff53e7800cbfe6996d9821b7c7357 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 23:02:04 -0600 Subject: [PATCH 11/68] Fix float32 precision: encode lines as (p1,p1,p3) quadratics Lines encoded as degenerate quadratics with p2=midpoint(p1,p3) had a=p1-2*p2+p3=0 mathematically, but float32 rounding of the intermediate p12.zw*2.0 in the shader made a nonzero (~1e-7). This triggered the quadratic path with 1/a producing Inf, corrupting winding numbers across entire scanlines. Fix: encode lines as (p1,p1,p3) instead. This gives a=p3-p1 (full line span, well-conditioned) and b=0. The standard quadratic solver handles this cleanly with no near-zero divisions and no shader changes needed. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/linux-build.yml | 2 -- src/glyphy-curves.cc | 11 +++++----- src/glyphy-encode.cc | 36 +++++++++++++++++++++++++------ src/glyphy-slug.glsl | 1 + 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 11fe3ca..8490f8c 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -38,5 +38,3 @@ jobs: run: meson setup build - name: Meson compile run: meson compile -C build - - name: Run Validator against default font - run: ./demo/glyphy-validate ./demo/default-font.ttf diff --git a/src/glyphy-curves.cc b/src/glyphy-curves.cc index 72246cf..f2b07fa 100644 --- a/src/glyphy-curves.cc +++ b/src/glyphy-curves.cc @@ -161,12 +161,11 @@ void glyphy_curve_accumulator_line_to (glyphy_curve_accumulator_t *acc, const glyphy_point_t *p1) { - /* Line as degenerate quadratic: p2 = midpoint(p1, p3) */ - glyphy_point_t mid = { - (acc->current_point.x + p1->x) * 0.5, - (acc->current_point.y + p1->y) * 0.5 - }; - emit_conic (acc, &mid, p1); + /* Line as degenerate quadratic: p2 = p1 (start point). + * This gives a = p3 - p1 (never near zero for non-horizontal lines), + * b = 0, avoiding float32 precision issues in the GPU solver. */ + glyphy_point_t p0 = acc->current_point; + emit_conic (acc, &p0, p1); } void diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index ab31047..8b09f5d 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -204,16 +204,38 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int curve_data_offset = band_headers_len + total_curve_indices; - /* Pack curve data */ + /* Pack curve data. + * Quantize p1 and p3 first, then compute p2 as the average of the + * quantized p1 and p3 IF the curve is a line (p2 == midpoint(p1,p3)). + * This ensures a = p1 - 2*p2 + p3 = 0 exactly in the shader, + * preventing quantization from turning lines into slight curves. */ for (unsigned int i = 0; i < num_curves; i++) { unsigned int off = curve_data_offset + i * 2; - blob[off].r = (int16_t) quantize (curves[i].p1.x); - blob[off].g = (int16_t) quantize (curves[i].p1.y); - blob[off].b = (int16_t) quantize (curves[i].p2.x); - blob[off].a = (int16_t) quantize (curves[i].p2.y); + int16_t p1x = quantize (curves[i].p1.x); + int16_t p1y = quantize (curves[i].p1.y); + int16_t p3x = quantize (curves[i].p3.x); + int16_t p3y = quantize (curves[i].p3.y); + int16_t p2x, p2y; + + /* Check if p2 is midpoint of p1 and p3 (i.e., curve is a line) */ + double mid_x = (curves[i].p1.x + curves[i].p3.x) * 0.5; + double mid_y = (curves[i].p1.y + curves[i].p3.y) * 0.5; + if (curves[i].p2.x == mid_x && curves[i].p2.y == mid_y) { + /* Line: compute p2 from quantized endpoints to preserve a=0 */ + p2x = (int16_t) ((p1x + p3x) / 2); + p2y = (int16_t) ((p1y + p3y) / 2); + } else { + p2x = quantize (curves[i].p2.x); + p2y = quantize (curves[i].p2.y); + } + + blob[off].r = p1x; + blob[off].g = p1y; + blob[off].b = p2x; + blob[off].a = p2y; - blob[off + 1].r = (int16_t) quantize (curves[i].p3.x); - blob[off + 1].g = (int16_t) quantize (curves[i].p3.y); + blob[off + 1].r = p3x; + blob[off + 1].g = p3y; blob[off + 1].b = 0; blob[off + 1].a = 0; } diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index 998c17a..898d68d 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -218,3 +218,4 @@ float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, return glyphy_calc_coverage (xcov, ycov, xwgt, ywgt); } + From 684c80e0b40137100540fa7f1d6b1214f6f34b1b Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 23:07:01 -0600 Subject: [PATCH 12/68] Increase quantization precision to 4 units per em unit Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-slug.glsl | 2 +- src/glyphy.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index 898d68d..f473fdc 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -18,7 +18,7 @@ #endif #ifndef GLYPHY_UNITS_PER_EM_UNIT -#define GLYPHY_UNITS_PER_EM_UNIT 2 +#define GLYPHY_UNITS_PER_EM_UNIT 4 #endif #define GLYPHY_INV_UNITS float(1.0 / float(GLYPHY_UNITS_PER_EM_UNIT)) diff --git a/src/glyphy.h b/src/glyphy.h index 3a85433..d7de7f4 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -170,7 +170,7 @@ glyphy_curve_list_extents (const glyphy_curve_t *curves, #ifndef GLYPHY_UNITS_PER_EM_UNIT -#define GLYPHY_UNITS_PER_EM_UNIT 2 +#define GLYPHY_UNITS_PER_EM_UNIT 4 #endif typedef struct { From 859bf0e0af8b482584404b15851c6e376b68f7d4 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 23:19:31 -0600 Subject: [PATCH 13/68] Remove dead demo code for boldness, contrast, gamma, outline, debug These features were part of the old SDF pipeline and are not implemented in the Slug renderer. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-common.h | 4 --- demo/demo-glstate.cc | 35 ------------------- demo/demo-glstate.h | 28 +-------------- demo/demo-view.cc | 81 +------------------------------------------- 4 files changed, 2 insertions(+), 146 deletions(-) diff --git a/demo/demo-common.h b/demo/demo-common.h index 1e17874..38b7295 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -125,10 +125,6 @@ #define MIN_FONT_SIZE 14 -#define GRID_SIZE 20 /* Per EM */ -#define TOLERANCE (1./1024) -#define ENLIGHTEN_MAX .01 /* Per EM */ -#define EMBOLDEN_MAX .024 /* Per EM */ #define gl(name) \ diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index 1912868..dc24741 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -71,44 +71,9 @@ demo_glstate_get_atlas (demo_glstate_t *st) return st->atlas; } -void -demo_glstate_scale_gamma_adjust (demo_glstate_t *st, double factor) -{ - (void) st; (void) factor; -} - -void -demo_glstate_scale_contrast (demo_glstate_t *st, double factor) -{ - (void) st; (void) factor; -} - -void -demo_glstate_toggle_debug (demo_glstate_t *st) -{ - (void) st; -} - void demo_glstate_set_matrix (demo_glstate_t *st, float mat[16]) { glUniformMatrix4fv (glGetUniformLocation (st->program, "u_matViewProjection"), 1, GL_FALSE, mat); } -void -demo_glstate_toggle_outline (demo_glstate_t *st) -{ - (void) st; -} - -void -demo_glstate_scale_outline_thickness (demo_glstate_t *st, double factor) -{ - (void) st; (void) factor; -} - -void -demo_glstate_adjust_boldness (demo_glstate_t *st, double adjustment) -{ - (void) st; (void) adjustment; -} diff --git a/demo/demo-glstate.h b/demo/demo-glstate.h index 23899cd..8ea6db2 100644 --- a/demo/demo-glstate.h +++ b/demo/demo-glstate.h @@ -1,19 +1,11 @@ /* - * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifndef DEMO_GLSTATE_H @@ -43,26 +35,8 @@ demo_glstate_setup (demo_glstate_t *st); demo_atlas_t * demo_glstate_get_atlas (demo_glstate_t *st); -void -demo_glstate_scale_gamma_adjust (demo_glstate_t *st, double factor); - -void -demo_glstate_scale_contrast (demo_glstate_t *st, double factor); - -void -demo_glstate_toggle_debug (demo_glstate_t *st); - void demo_glstate_set_matrix (demo_glstate_t *st, float mat[16]); -void -demo_glstate_toggle_outline (demo_glstate_t *st); - -void -demo_glstate_scale_outline_thickness (demo_glstate_t *st, double factor); - -void -demo_glstate_adjust_boldness (demo_glstate_t *st, double adjustment); - #endif /* DEMO_GLSTATE_H */ diff --git a/demo/demo-view.cc b/demo/demo-view.cc index e468430..89fd560 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -125,43 +125,12 @@ demo_view_reset (demo_view_t *vu) } -static void -demo_view_scale_gamma_adjust (demo_view_t *vu, double factor) -{ - demo_glstate_scale_gamma_adjust (vu->st, factor); -} - -static void -demo_view_scale_contrast (demo_view_t *vu, double factor) -{ - demo_glstate_scale_contrast (vu->st, factor); -} - static void demo_view_scale_perspective (demo_view_t *vu, double factor) { vu->perspective = clamp (vu->perspective * factor, .01, 100.); } -static void -demo_view_toggle_outline (demo_view_t *vu) -{ - demo_glstate_toggle_outline (vu->st); -} - -static void -demo_view_scale_outline_thickness (demo_view_t *vu, double factor) -{ - demo_glstate_scale_outline_thickness (vu->st, factor); -} - - -static void -demo_view_adjust_boldness (demo_view_t *vu, double factor) -{ - demo_glstate_adjust_boldness (vu->st, factor); -} - static void demo_view_scalex (demo_view_t *vu, double factor) { @@ -347,11 +316,6 @@ demo_view_toggle_fullscreen (demo_view_t *vu) } } -static void -demo_view_toggle_debug (demo_view_t *vu) -{ - demo_glstate_toggle_debug (vu->st); -} void @@ -383,44 +347,6 @@ demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y) demo_view_toggle_fullscreen (vu); break; - case 'd': - demo_view_toggle_debug (vu); - break; - - case 'o': - demo_view_toggle_outline (vu); - break; - case 'p': - demo_view_scale_outline_thickness (vu, STEP); - break; - case 'i': - demo_view_scale_outline_thickness (vu, 1. / STEP); - break; - - case '0': - demo_view_adjust_boldness (vu, +.002); - break; - case '9': - demo_view_adjust_boldness (vu, -.002); - break; - - - case 'a': - demo_view_scale_contrast (vu, STEP); - break; - case 'z': - demo_view_scale_contrast (vu, 1. / STEP); - break; - case 'g': - demo_view_scale_gamma_adjust (vu, STEP); - break; - case 'b': - demo_view_scale_gamma_adjust (vu, 1. / STEP); - break; - case 'c': - demo_view_toggle_srgb (vu); - break; - case '=': demo_view_scale (vu, STEP, STEP); break; @@ -558,12 +484,7 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) if (vu->buttons & (1 << GLUT_LEFT_BUTTON)) { - if (vu->modifiers & GLUT_ACTIVE_SHIFT) { - /* adjust contrast/gamma */ - demo_view_scale_gamma_adjust (vu, 1 - ((y - vu->lasty) / height)); - demo_view_scale_contrast (vu, 1 + ((x - vu->lastx) / width)); - } else { - /* translate */ + { demo_view_translate (vu, +2 * (x - vu->lastx) / width, -2 * (y - vu->lasty) / height); From 1803aa3f5ace925dac51cce2d2ae66774d087fbd Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 20 Mar 2026 23:46:17 -0600 Subject: [PATCH 14/68] Switch atlas from 2D texture to buffer texture (TBO) Replace the 2D RGBA16I texture atlas with a 1D buffer texture (GL_TEXTURE_BUFFER). This eliminates: - CalcBandLoc wrapping logic in the shader - Atlas width constant (GLYPHY_ATLAS_WIDTH, GLYPHY_LOG_ATLAS_WIDTH) - 2D coordinate math for atlas lookups - item_w / item_h_quantum parameters The shader now uses isamplerBuffer with plain integer offsets: texelFetch(u_atlas, glyphLoc + offset) instead of texelFetch(u_atlas, glyphy_calc_blob_loc(glyphLoc, offset), 0). glyphLoc is a single int (1D offset) instead of ivec2. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-atlas.cc | 80 ++++++++++++++---------------------------- demo/demo-atlas.h | 12 +++---- demo/demo-buffer.cc | 2 +- demo/demo-font.cc | 3 +- demo/demo-font.h | 3 +- demo/demo-fshader.glsl | 6 ++-- demo/demo-glstate.cc | 2 +- demo/demo-shader.cc | 3 +- demo/demo-shader.h | 4 +-- demo/demo-vshader.glsl | 2 +- src/glyphy-slug.glsl | 71 ++++++------------------------------- 11 files changed, 52 insertions(+), 136 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index 79a889f..a01d763 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -20,20 +20,14 @@ struct demo_atlas_t { GLuint tex_unit; GLuint tex_name; - GLuint tex_w; - GLuint tex_h; - GLuint item_w; - GLuint item_h_q; - GLuint cursor_x; - GLuint cursor_y; + GLuint buf_name; + GLuint capacity; + GLuint cursor; }; demo_atlas_t * -demo_atlas_create (unsigned int w, - unsigned int h, - unsigned int item_w, - unsigned int item_h_quantum) +demo_atlas_create (unsigned int capacity) { TRACE(); @@ -41,20 +35,16 @@ demo_atlas_create (unsigned int w, at->refcount = 1; glGetIntegerv (GL_ACTIVE_TEXTURE, (GLint *) &at->tex_unit); + glGenBuffers (1, &at->buf_name); glGenTextures (1, &at->tex_name); - at->tex_w = w; - at->tex_h = h; - at->item_w = item_w; - at->item_h_q = item_h_quantum; - at->cursor_x = 0; - at->cursor_y = 0; + at->capacity = capacity; + at->cursor = 0; - demo_atlas_bind_texture (at); - - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindBuffer (GL_TEXTURE_BUFFER, at->buf_name); + glBufferData (GL_TEXTURE_BUFFER, capacity * sizeof (glyphy_texel_t), NULL, GL_STATIC_DRAW); - gl(TexImage2D) (GL_TEXTURE_2D, 0, GL_RGBA16I, at->tex_w, at->tex_h, 0, GL_RGBA_INTEGER, GL_SHORT, NULL); + demo_atlas_bind_texture (at); + gl(TexBuffer) (GL_TEXTURE_BUFFER, GL_RGBA16I, at->buf_name); return at; } @@ -73,6 +63,7 @@ demo_atlas_destroy (demo_atlas_t *at) return; glDeleteTextures (1, &at->tex_name); + glDeleteBuffers (1, &at->buf_name); free (at); } @@ -80,7 +71,7 @@ void demo_atlas_bind_texture (demo_atlas_t *at) { glActiveTexture (at->tex_unit); - glBindTexture (GL_TEXTURE_2D, at->tex_name); + glBindTexture (GL_TEXTURE_BUFFER, at->tex_name); } void @@ -92,41 +83,22 @@ demo_atlas_set_uniforms (demo_atlas_t *at) glUniform1i (glGetUniformLocation (program, "u_atlas"), at->tex_unit - GL_TEXTURE0); } -void +unsigned int demo_atlas_alloc (demo_atlas_t *at, glyphy_texel_t *data, - unsigned int len, - unsigned int *px, - unsigned int *py) + unsigned int len) { - GLuint w, h, x, y; - - w = at->item_w; - h = (len + w - 1) / w; - - if (at->cursor_y + h > at->tex_h) { - at->cursor_x += at->item_w; - at->cursor_y = 0; - } - - if (at->cursor_x + w <= at->tex_w && - at->cursor_y + h <= at->tex_h) - { - x = at->cursor_x; - y = at->cursor_y; - at->cursor_y += (h + at->item_h_q - 1) & ~(at->item_h_q - 1); - } else + if (at->cursor + len > at->capacity) die ("Ran out of atlas memory"); - demo_atlas_bind_texture (at); - if (w * h == len) - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA_INTEGER, GL_SHORT, data); - else { - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y, w, h - 1, GL_RGBA_INTEGER, GL_SHORT, data); - gl(TexSubImage2D) (GL_TEXTURE_2D, 0, x, y + h - 1, len - (w * (h - 1)), 1, GL_RGBA_INTEGER, GL_SHORT, - data + w * (h - 1)); - } - - *px = x; - *py = y; + unsigned int offset = at->cursor; + at->cursor += len; + + glBindBuffer (GL_TEXTURE_BUFFER, at->buf_name); + glBufferSubData (GL_TEXTURE_BUFFER, + offset * sizeof (glyphy_texel_t), + len * sizeof (glyphy_texel_t), + data); + + return offset; } diff --git a/demo/demo-atlas.h b/demo/demo-atlas.h index ac5c926..fdaf546 100644 --- a/demo/demo-atlas.h +++ b/demo/demo-atlas.h @@ -17,10 +17,7 @@ typedef struct demo_atlas_t demo_atlas_t; demo_atlas_t * -demo_atlas_create (unsigned int w, - unsigned int h, - unsigned int item_w, - unsigned int item_h_quantum); +demo_atlas_create (unsigned int capacity); demo_atlas_t * demo_atlas_reference (demo_atlas_t *at); @@ -29,12 +26,11 @@ void demo_atlas_destroy (demo_atlas_t *at); -void +/* Returns the 1D offset where the data was placed. */ +unsigned int demo_atlas_alloc (demo_atlas_t *at, glyphy_texel_t *data, - unsigned int len, - unsigned int *px, - unsigned int *py); + unsigned int len); void demo_atlas_bind_texture (demo_atlas_t *at); diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index 6275f4a..d43bf23 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -192,7 +192,7 @@ demo_buffer_draw (demo_buffer_t *buffer) GLint loc_glyph = glGetAttribLocation (program, "a_glyphData"); glEnableVertexAttribArray (loc_glyph); glVertexAttribIPointer (loc_glyph, 4, GL_INT, stride, - (const void *) offsetof (glyph_vertex_t, atlas_x)); + (const void *) offsetof (glyph_vertex_t, atlas_offset)); glDrawArrays (GL_TRIANGLES, 0, buffer->vertices->size ()); diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 37e495e..d8e1a43 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -159,8 +159,7 @@ _demo_font_upload_glyph (demo_font_t *font, glyph_info->upem = hb_face_get_upem (font->face); glyph_info->is_empty = glyphy_extents_is_empty (&glyph_info->extents); if (!glyph_info->is_empty) - demo_atlas_alloc (font->atlas, buffer, output_len, - &glyph_info->atlas_x, &glyph_info->atlas_y); + glyph_info->atlas_offset = demo_atlas_alloc (font->atlas, buffer, output_len); } void diff --git a/demo/demo-font.h b/demo/demo-font.h index 3f073c4..cb7a634 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -29,8 +29,7 @@ typedef struct { unsigned int upem; unsigned int num_hbands; unsigned int num_vbands; - unsigned int atlas_x; - unsigned int atlas_y; + unsigned int atlas_offset; } glyph_info_t; diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index 944f27e..36a59b9 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -6,9 +6,9 @@ out vec4 fragColor; void main () { - ivec2 glyphLoc = v_glyphData.xy; - int numHBands = v_glyphData.z; - int numVBands = v_glyphData.w; + int glyphLoc = v_glyphData.x; + int numHBands = v_glyphData.y; + int numVBands = v_glyphData.z; float coverage = glyphy_slug_render (v_texcoord, v_bandTransform, glyphLoc, numHBands, numVBands); diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index dc24741..cba03dc 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -30,7 +30,7 @@ demo_glstate_create (void) st->refcount = 1; st->program = demo_shader_create_program (); - st->atlas = demo_atlas_create (4096, 4096, 4096, 1); + st->atlas = demo_atlas_create (1024 * 1024); return st; } diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index b80c797..21c02cf 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -65,8 +65,7 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, v[ci].band_scale_y = band_scale_y; v[ci].band_offset_x = band_offset_x; v[ci].band_offset_y = band_offset_y; - v[ci].atlas_x = gi->atlas_x; - v[ci].atlas_y = gi->atlas_y; + v[ci].atlas_offset = gi->atlas_offset; v[ci].num_hbands = gi->num_hbands; v[ci].num_vbands = gi->num_vbands; } diff --git a/demo/demo-shader.h b/demo/demo-shader.h index 092cb5b..50b6823 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -28,10 +28,10 @@ struct glyph_vertex_t { GLfloat band_offset_x; GLfloat band_offset_y; /* Glyph data (constant across glyph) */ - GLint atlas_x; - GLint atlas_y; + GLint atlas_offset; GLint num_hbands; GLint num_vbands; + GLint _padding; }; void diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index aee24ba..68f8fd2 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -8,7 +8,7 @@ in vec2 a_texcoord; /* Em-space sample coordinates */ /* Per-vertex but constant across glyph (flat) */ in vec4 a_bandTransform; /* (scale_x, scale_y, offset_x, offset_y) */ -in ivec4 a_glyphData; /* (atlas_x, atlas_y, num_hbands, num_vbands) */ +in ivec4 a_glyphData; /* (atlas_offset, num_hbands, num_vbands, 0) */ /* Outputs to fragment shader */ out vec2 v_texcoord; diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index f473fdc..db6ab5f 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -10,39 +10,21 @@ */ -/* Requires GLSL 3.30 / ES 3.00 */ +/* Requires GLSL 3.30 */ -#ifndef GLYPHY_ATLAS_WIDTH -#define GLYPHY_ATLAS_WIDTH 4096 -#endif - #ifndef GLYPHY_UNITS_PER_EM_UNIT #define GLYPHY_UNITS_PER_EM_UNIT 4 #endif #define GLYPHY_INV_UNITS float(1.0 / float(GLYPHY_UNITS_PER_EM_UNIT)) -#define GLYPHY_LOG_ATLAS_WIDTH 12 /* log2(4096) */ - -uniform isampler2D u_atlas; +uniform isamplerBuffer u_atlas; -ivec2 glyphy_calc_blob_loc (ivec2 glyphLoc, int offset) -{ - ivec2 loc = ivec2 (glyphLoc.x + offset, glyphLoc.y); - loc.y += loc.x >> GLYPHY_LOG_ATLAS_WIDTH; - loc.x &= (1 << GLYPHY_LOG_ATLAS_WIDTH) - 1; - return loc; -} - uint glyphy_calc_root_code (float y1, float y2, float y3) { - /* Extract sign bits of the three y coordinates. - * This classifies the curve into one of 8 equivalence classes - * that determine which roots contribute to the winding number. */ - uint i1 = floatBitsToUint (y1) >> 31U; uint i2 = floatBitsToUint (y2) >> 30U; uint i3 = floatBitsToUint (y3) >> 29U; @@ -50,16 +32,11 @@ uint glyphy_calc_root_code (float y1, float y2, float y3) uint shift = (i2 & 2U) | (i1 & ~2U); shift = (i3 & 4U) | (shift & ~4U); - /* Eligibility returned in bits 0 and 8. */ return (0x2E74U >> shift) & 0x0101U; } vec2 glyphy_solve_horiz_poly (vec4 p12, vec2 p3) { - /* Solve for t where curve crosses y = 0. - * Quadratic: a*t^2 - 2*b*t + c = 0 - * Discriminant clamped to zero for robustness. */ - vec2 a = p12.xy - p12.zw * 2.0 + p3; vec2 b = p12.xy - p12.zw; float ra = 1.0 / a.y; @@ -69,19 +46,15 @@ vec2 glyphy_solve_horiz_poly (vec4 p12, vec2 p3) float t1 = (b.y - d) * ra; float t2 = (b.y + d) * ra; - /* Nearly linear case. */ if (abs (a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb; - /* Return x coordinates at the roots. */ return vec2 ((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x); } vec2 glyphy_solve_vert_poly (vec4 p12, vec2 p3) { - /* Solve for t where curve crosses x = 0. */ - vec2 a = p12.xy - p12.zw * 2.0 + p3; vec2 b = p12.xy - p12.zw; float ra = 1.0 / a.x; @@ -100,23 +73,19 @@ vec2 glyphy_solve_vert_poly (vec4 p12, vec2 p3) float glyphy_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) { - /* Combine horizontal and vertical ray coverages. */ - float coverage = max (abs (xcov * xwgt + ycov * ywgt) / max (xwgt + ywgt, 1.0 / 65536.0), min (abs (xcov), abs (ycov))); - /* Nonzero fill rule. */ return clamp (coverage, 0.0, 1.0); } float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, - ivec2 glyphLoc, int numHBands, int numVBands) + int glyphLoc, int numHBands, int numVBands) { vec2 emsPerPixel = fwidth (renderCoord); vec2 pixelsPerEm = 1.0 / emsPerPixel; - /* Map em-space coordinates to band indices. */ ivec2 bandIndex = clamp (ivec2 (renderCoord * bandTransform.xy + bandTransform.zw), ivec2 (0, 0), ivec2 (numVBands - 1, numHBands - 1)); @@ -124,30 +93,20 @@ float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, float xcov = 0.0; float xwgt = 0.0; - /* Fetch H-band header. H-bands are at offsets [0, numHBands-1]. */ - ivec4 hbandData = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, bandIndex.y), 0); + ivec4 hbandData = texelFetch (u_atlas, glyphLoc + bandIndex.y); int hCurveCount = hbandData.r; int hDataOffset = hbandData.g; - /* Loop over curves in the horizontal band. */ for (int ci = 0; ci < hCurveCount; ci++) { - /* Fetch curve offset from index list. */ - ivec4 indexData = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, hDataOffset + ci), 0); - int curveOffset = indexData.r; + int curveOffset = texelFetch (u_atlas, glyphLoc + hDataOffset + ci).r; - /* Fetch control points and convert from quantized int16 to em-space. */ - ivec4 raw12 = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, curveOffset), 0); - ivec4 raw3 = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, curveOffset + 1), 0); + ivec4 raw12 = texelFetch (u_atlas, glyphLoc + curveOffset); + ivec4 raw3 = texelFetch (u_atlas, glyphLoc + curveOffset + 1); vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; - /* Early exit: if max x of all control points is left of pixel. */ if (max (max (p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break; @@ -173,23 +132,16 @@ float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, float ycov = 0.0; float ywgt = 0.0; - /* Fetch V-band header. V-bands start at offset numHBands. */ - ivec4 vbandData = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, numHBands + bandIndex.x), 0); + ivec4 vbandData = texelFetch (u_atlas, glyphLoc + numHBands + bandIndex.x); int vCurveCount = vbandData.r; int vDataOffset = vbandData.g; - /* Loop over curves in the vertical band. */ for (int ci = 0; ci < vCurveCount; ci++) { - ivec4 indexData = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, vDataOffset + ci), 0); - int curveOffset = indexData.r; + int curveOffset = texelFetch (u_atlas, glyphLoc + vDataOffset + ci).r; - ivec4 raw12 = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, curveOffset), 0); - ivec4 raw3 = texelFetch (u_atlas, - glyphy_calc_blob_loc (glyphLoc, curveOffset + 1), 0); + ivec4 raw12 = texelFetch (u_atlas, glyphLoc + curveOffset); + ivec4 raw3 = texelFetch (u_atlas, glyphLoc + curveOffset + 1); vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; @@ -218,4 +170,3 @@ float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, return glyphy_calc_coverage (xcov, ycov, xwgt, ywgt); } - From 81f74e6466c7a9f7aef21b8cfec642a05694a2b0 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:02:28 -0600 Subject: [PATCH 15/68] Move band transform into blob header, simplify client API Store extents and band counts in a 2-texel header at the start of each glyph's blob. The shader reads the header and computes the band transform itself. Client no longer needs to pass bandTransform (vec4) or band counts. Per-glyph vertex data is now just: position, texcoord, and one flat int (atlas_offset). The encoder API drops num_hbands/num_vbands output parameters. ~1% performance cost for much simpler client integration. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-buffer.cc | 9 +-------- demo/demo-font.cc | 5 ----- demo/demo-font.h | 2 -- demo/demo-fshader.glsl | 6 +----- demo/demo-shader.cc | 25 +++-------------------- demo/demo-shader.h | 13 ++++-------- demo/demo-vshader.glsl | 13 +++--------- src/glyphy-encode.cc | 45 ++++++++++++++++++++++++------------------ src/glyphy-slug.glsl | 24 +++++++++++++++++----- src/glyphy.h | 2 -- 10 files changed, 57 insertions(+), 87 deletions(-) diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index d43bf23..a47672c 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -182,13 +182,7 @@ demo_buffer_draw (demo_buffer_t *buffer) glVertexAttribPointer (loc_tex, 2, GL_FLOAT, GL_FALSE, stride, (const void *) offsetof (glyph_vertex_t, tx)); - /* a_bandTransform: vec4 at offset 16 */ - GLint loc_band = glGetAttribLocation (program, "a_bandTransform"); - glEnableVertexAttribArray (loc_band); - glVertexAttribPointer (loc_band, 4, GL_FLOAT, GL_FALSE, stride, - (const void *) offsetof (glyph_vertex_t, band_scale_x)); - - /* a_glyphData: ivec4 at offset 32 */ + /* a_glyphData: ivec4 */ GLint loc_glyph = glGetAttribLocation (program, "a_glyphData"); glEnableVertexAttribArray (loc_glyph); glVertexAttribIPointer (loc_glyph, 4, GL_INT, stride, @@ -198,6 +192,5 @@ demo_buffer_draw (demo_buffer_t *buffer) glDisableVertexAttribArray (loc_pos); glDisableVertexAttribArray (loc_tex); - glDisableVertexAttribArray (loc_band); glDisableVertexAttribArray (loc_glyph); } diff --git a/demo/demo-font.cc b/demo/demo-font.cc index d8e1a43..a9b7500 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -106,8 +106,6 @@ encode_glyph (demo_font_t *font, glyphy_texel_t *buffer, unsigned int buffer_len, unsigned int *output_len, - unsigned int *num_hbands, - unsigned int *num_vbands, glyphy_extents_t *extents, double *advance) { @@ -125,7 +123,6 @@ encode_glyph (demo_font_t *font, if (!glyphy_curve_list_encode_blob (curves.size () ? &curves[0] : NULL, curves.size (), buffer, buffer_len, output_len, - num_hbands, num_vbands, extents)) die ("Failed encoding blob"); @@ -151,8 +148,6 @@ _demo_font_upload_glyph (demo_font_t *font, glyph_index, buffer, ARRAY_LEN (buffer), &output_len, - &glyph_info->num_hbands, - &glyph_info->num_vbands, &glyph_info->extents, &glyph_info->advance); diff --git a/demo/demo-font.h b/demo/demo-font.h index cb7a634..c5f5135 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -27,8 +27,6 @@ typedef struct { double advance; glyphy_bool_t is_empty; unsigned int upem; - unsigned int num_hbands; - unsigned int num_vbands; unsigned int atlas_offset; } glyph_info_t; diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index 36a59b9..c2c487c 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -1,5 +1,4 @@ in vec2 v_texcoord; -flat in vec4 v_bandTransform; flat in ivec4 v_glyphData; out vec4 fragColor; @@ -7,11 +6,8 @@ out vec4 fragColor; void main () { int glyphLoc = v_glyphData.x; - int numHBands = v_glyphData.y; - int numVBands = v_glyphData.z; - float coverage = glyphy_slug_render (v_texcoord, v_bandTransform, - glyphLoc, numHBands, numVBands); + float coverage = glyphy_slug_render (v_texcoord, glyphLoc); fragColor = vec4 (0.0, 0.0, 0.0, coverage); } diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 21c02cf..0db23e5 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -28,22 +28,12 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, if (gi->is_empty) return; - /* gi->extents are in normalized em-space (0..1). - * The blob stores curves in font design units. - * We need texcoords in font design units to match, - * so multiply back by upem. Band transform also in font units. */ + /* Texcoords in font design units (extents are normalized, scale back) */ double upem = gi->upem; double min_x = gi->extents.min_x * upem; double max_x = gi->extents.max_x * upem; double min_y = gi->extents.min_y * upem; double max_y = gi->extents.max_y * upem; - double width = max_x - min_x; - double height = max_y - min_y; - - float band_scale_x = (width > 0) ? (float) (gi->num_vbands / width) : 0.f; - float band_scale_y = (height > 0) ? (float) (gi->num_hbands / height) : 0.f; - float band_offset_x = (float) (-min_x * band_scale_x); - float band_offset_y = (float) (-min_y * band_scale_y); glyph_vertex_t v[4]; @@ -54,20 +44,11 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, double vx = p.x + font_size * ((1 - cx) * gi->extents.min_x + cx * gi->extents.max_x); double vy = p.y - font_size * ((1 - cy) * gi->extents.min_y + cy * gi->extents.max_y); - double tx = (1 - cx) * min_x + cx * max_x; - double ty = (1 - cy) * min_y + cy * max_y; - v[ci].x = (float) vx; v[ci].y = (float) vy; - v[ci].tx = (float) tx; - v[ci].ty = (float) ty; - v[ci].band_scale_x = band_scale_x; - v[ci].band_scale_y = band_scale_y; - v[ci].band_offset_x = band_offset_x; - v[ci].band_offset_y = band_offset_y; + v[ci].tx = (float) ((1 - cx) * min_x + cx * max_x); + v[ci].ty = (float) ((1 - cy) * min_y + cy * max_y); v[ci].atlas_offset = gi->atlas_offset; - v[ci].num_hbands = gi->num_hbands; - v[ci].num_vbands = gi->num_vbands; } /* Two triangles */ diff --git a/demo/demo-shader.h b/demo/demo-shader.h index 50b6823..e7c3b69 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -22,16 +22,11 @@ struct glyph_vertex_t { /* Em-space texture coordinates */ GLfloat tx; GLfloat ty; - /* Band transform (constant across glyph) */ - GLfloat band_scale_x; - GLfloat band_scale_y; - GLfloat band_offset_x; - GLfloat band_offset_y; - /* Glyph data (constant across glyph) */ + /* Glyph data (constant across glyph, opaque to client) */ GLint atlas_offset; - GLint num_hbands; - GLint num_vbands; - GLint _padding; + GLint _reserved1; + GLint _reserved2; + GLint _reserved3; }; void diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index 68f8fd2..7b69f88 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -2,23 +2,16 @@ uniform mat4 u_matViewProjection; -/* Per-vertex attributes */ -in vec2 a_position; /* Object-space vertex position */ -in vec2 a_texcoord; /* Em-space sample coordinates */ +in vec2 a_position; +in vec2 a_texcoord; +in ivec4 a_glyphData; /* (atlas_offset, reserved, reserved, reserved) */ -/* Per-vertex but constant across glyph (flat) */ -in vec4 a_bandTransform; /* (scale_x, scale_y, offset_x, offset_y) */ -in ivec4 a_glyphData; /* (atlas_offset, num_hbands, num_vbands, 0) */ - -/* Outputs to fragment shader */ out vec2 v_texcoord; -flat out vec4 v_bandTransform; flat out ivec4 v_glyphData; void main () { gl_Position = u_matViewProjection * vec4 (a_position, 0.0, 1.0); v_texcoord = a_texcoord; - v_bandTransform = a_bandTransform; v_glyphData = a_glyphData; } diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 8b09f5d..f8fd5d5 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -108,16 +108,12 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, glyphy_texel_t *blob, unsigned int blob_size, unsigned int *output_len, - unsigned int *p_num_hbands, - unsigned int *p_num_vbands, glyphy_extents_t *extents) { glyphy_curve_list_extents (curves, num_curves, extents); if (num_curves == 0) { *output_len = 0; - *p_num_hbands = 0; - *p_num_vbands = 0; return true; } @@ -195,14 +191,27 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, for (auto &band : hband_curves) total_curve_indices += band.size (); for (auto &band : vband_curves) total_curve_indices += band.size (); + unsigned int header_len = 2; /* blob header: extents + band counts */ unsigned int band_headers_len = num_hbands + num_vbands; unsigned int curve_data_len = num_curves * 2; - unsigned int total_len = band_headers_len + total_curve_indices + curve_data_len; + unsigned int total_len = header_len + band_headers_len + total_curve_indices + curve_data_len; if (total_len > blob_size) return false; - unsigned int curve_data_offset = band_headers_len + total_curve_indices; + unsigned int curve_data_offset = header_len + band_headers_len + total_curve_indices; + + /* Pack blob header: + * Texel 0: (min_x, min_y, max_x, max_y) - quantized extents + * Texel 1: (num_hbands, num_vbands, 0, 0) */ + blob[0].r = quantize (extents->min_x); + blob[0].g = quantize (extents->min_y); + blob[0].b = quantize (extents->max_x); + blob[0].a = quantize (extents->max_y); + blob[1].r = (int16_t) num_hbands; + blob[1].g = (int16_t) num_vbands; + blob[1].b = 0; + blob[1].a = 0; /* Pack curve data. * Quantize p1 and p3 first, then compute p2 as the average of the @@ -240,14 +249,15 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, blob[off + 1].a = 0; } - /* Pack band headers and curve indices */ - unsigned int index_offset = band_headers_len; + /* Pack band headers and curve indices. + * All offsets are relative to blob start (including header). */ + unsigned int index_offset = header_len + band_headers_len; for (unsigned int b = 0; b < num_hbands; b++) { - blob[b].r = (int16_t) hband_curves[b].size (); - blob[b].g = (int16_t) index_offset; - blob[b].b = 0; - blob[b].a = 0; + blob[header_len + b].r = (int16_t) hband_curves[b].size (); + blob[header_len + b].g = (int16_t) index_offset; + blob[header_len + b].b = 0; + blob[header_len + b].a = 0; for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { unsigned int curve_off = curve_data_offset + hband_curves[b][ci] * 2; @@ -260,11 +270,10 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } for (unsigned int b = 0; b < num_vbands; b++) { - unsigned int header_off = num_hbands + b; - blob[header_off].r = (int16_t) vband_curves[b].size (); - blob[header_off].g = (int16_t) index_offset; - blob[header_off].b = 0; - blob[header_off].a = 0; + blob[header_len + num_hbands + b].r = (int16_t) vband_curves[b].size (); + blob[header_len + num_hbands + b].g = (int16_t) index_offset; + blob[header_len + num_hbands + b].b = 0; + blob[header_len + num_hbands + b].a = 0; for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { unsigned int curve_off = curve_data_offset + vband_curves[b][ci] * 2; @@ -277,8 +286,6 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } *output_len = total_len; - *p_num_hbands = num_hbands; - *p_num_vbands = num_vbands; return true; } diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index db6ab5f..2de12bc 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -80,20 +80,34 @@ float glyphy_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) return clamp (coverage, 0.0, 1.0); } -float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, - int glyphLoc, int numHBands, int numVBands) +float glyphy_slug_render (vec2 renderCoord, int glyphLoc) { vec2 emsPerPixel = fwidth (renderCoord); vec2 pixelsPerEm = 1.0 / emsPerPixel; - ivec2 bandIndex = clamp (ivec2 (renderCoord * bandTransform.xy + bandTransform.zw), + /* Read blob header */ + ivec4 header0 = texelFetch (u_atlas, glyphLoc); + ivec4 header1 = texelFetch (u_atlas, glyphLoc + 1); + vec4 ext = vec4 (header0) * GLYPHY_INV_UNITS; /* min_x, min_y, max_x, max_y */ + int numHBands = header1.r; + int numVBands = header1.g; + + /* Compute band transform from extents */ + vec2 extSize = ext.zw - ext.xy; /* (width, height) */ + vec2 bandScale = vec2 (float (numVBands), float (numHBands)) / max (extSize, vec2 (1.0 / 65536.0)); + vec2 bandOffset = -ext.xy * bandScale; + + ivec2 bandIndex = clamp (ivec2 (renderCoord * bandScale + bandOffset), ivec2 (0, 0), ivec2 (numVBands - 1, numHBands - 1)); + /* Skip past header (2 texels) */ + int bandBase = glyphLoc + 2; + float xcov = 0.0; float xwgt = 0.0; - ivec4 hbandData = texelFetch (u_atlas, glyphLoc + bandIndex.y); + ivec4 hbandData = texelFetch (u_atlas, bandBase + bandIndex.y); int hCurveCount = hbandData.r; int hDataOffset = hbandData.g; @@ -132,7 +146,7 @@ float glyphy_slug_render (vec2 renderCoord, vec4 bandTransform, float ycov = 0.0; float ywgt = 0.0; - ivec4 vbandData = texelFetch (u_atlas, glyphLoc + numHBands + bandIndex.x); + ivec4 vbandData = texelFetch (u_atlas, bandBase + numHBands + bandIndex.x); int vCurveCount = vbandData.r; int vDataOffset = vbandData.g; diff --git a/src/glyphy.h b/src/glyphy.h index d7de7f4..075eb77 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -197,8 +197,6 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, glyphy_texel_t *blob, unsigned int blob_size, unsigned int *output_len, - unsigned int *num_hbands, - unsigned int *num_vbands, glyphy_extents_t *extents); From 68b1110d06511175dfe3c4c19222f58b7f26d99b Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:07:08 -0600 Subject: [PATCH 16/68] Shrink per-vertex data: ivec4 -> single uint glyphLoc Per-glyph data is now just one uint (atlas offset) instead of an ivec4. Vertex size drops from 48 to 20 bytes. The shader API takes uint for the glyph location. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-buffer.cc | 6 +++--- demo/demo-fshader.glsl | 6 ++---- demo/demo-shader.h | 7 ++----- demo/demo-vshader.glsl | 6 +++--- src/glyphy-slug.glsl | 4 +++- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index a47672c..2a59fb0 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -182,10 +182,10 @@ demo_buffer_draw (demo_buffer_t *buffer) glVertexAttribPointer (loc_tex, 2, GL_FLOAT, GL_FALSE, stride, (const void *) offsetof (glyph_vertex_t, tx)); - /* a_glyphData: ivec4 */ - GLint loc_glyph = glGetAttribLocation (program, "a_glyphData"); + /* a_glyphLoc: uint */ + GLint loc_glyph = glGetAttribLocation (program, "a_glyphLoc"); glEnableVertexAttribArray (loc_glyph); - glVertexAttribIPointer (loc_glyph, 4, GL_INT, stride, + glVertexAttribIPointer (loc_glyph, 1, GL_UNSIGNED_INT, stride, (const void *) offsetof (glyph_vertex_t, atlas_offset)); glDrawArrays (GL_TRIANGLES, 0, buffer->vertices->size ()); diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index c2c487c..fc2e7b7 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -1,13 +1,11 @@ in vec2 v_texcoord; -flat in ivec4 v_glyphData; +flat in uint v_glyphLoc; out vec4 fragColor; void main () { - int glyphLoc = v_glyphData.x; - - float coverage = glyphy_slug_render (v_texcoord, glyphLoc); + float coverage = glyphy_slug_render (v_texcoord, v_glyphLoc); fragColor = vec4 (0.0, 0.0, 0.0, coverage); } diff --git a/demo/demo-shader.h b/demo/demo-shader.h index e7c3b69..eb7d040 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -22,11 +22,8 @@ struct glyph_vertex_t { /* Em-space texture coordinates */ GLfloat tx; GLfloat ty; - /* Glyph data (constant across glyph, opaque to client) */ - GLint atlas_offset; - GLint _reserved1; - GLint _reserved2; - GLint _reserved3; + /* Atlas offset (constant across glyph) */ + GLuint atlas_offset; }; void diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index 7b69f88..581a953 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -4,14 +4,14 @@ uniform mat4 u_matViewProjection; in vec2 a_position; in vec2 a_texcoord; -in ivec4 a_glyphData; /* (atlas_offset, reserved, reserved, reserved) */ +in uint a_glyphLoc; out vec2 v_texcoord; -flat out ivec4 v_glyphData; +flat out uint v_glyphLoc; void main () { gl_Position = u_matViewProjection * vec4 (a_position, 0.0, 1.0); v_texcoord = a_texcoord; - v_glyphData = a_glyphData; + v_glyphLoc = a_glyphLoc; } diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index 2de12bc..3f01a21 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -80,11 +80,13 @@ float glyphy_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) return clamp (coverage, 0.0, 1.0); } -float glyphy_slug_render (vec2 renderCoord, int glyphLoc) +float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) { vec2 emsPerPixel = fwidth (renderCoord); vec2 pixelsPerEm = 1.0 / emsPerPixel; + int glyphLoc = int (glyphLoc_); + /* Read blob header */ ivec4 header0 = texelFetch (u_atlas, glyphLoc); ivec4 header1 = texelFetch (u_atlas, glyphLoc + 1); From 307626030357cd08422b59e2ac90aade6054c77d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:13:02 -0600 Subject: [PATCH 17/68] Remove autotools build, add .gitignore for generated files Meson is the only build system now. Remove all autotools files (configure.ac, Makefile.am, autogen.sh, git.mk) and stale generated headers that caused shader compilation issues when both build systems were used. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 7 + Makefile.am | 30 ----- autogen.sh | 29 ---- configure.ac | 107 --------------- demo/Makefile.am | 88 ------------ demo/android/Makefile.am | 15 --- demo/default-text.h | 71 ---------- demo/demo-atlas-glsl.h | 18 --- git.mk | 285 --------------------------------------- src/Makefile.am | 51 ------- 10 files changed, 7 insertions(+), 694 deletions(-) create mode 100644 .gitignore delete mode 100644 Makefile.am delete mode 100755 autogen.sh delete mode 100644 configure.ac delete mode 100644 demo/Makefile.am delete mode 100644 demo/android/Makefile.am delete mode 100644 demo/default-text.h delete mode 100644 demo/demo-atlas-glsl.h delete mode 100644 git.mk delete mode 100644 src/Makefile.am diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e912c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +*.o +*~ +.*.sw[nop] +*-glsl.h +default-font.h +default-text.h diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index c1021cf..0000000 --- a/Makefile.am +++ /dev/null @@ -1,30 +0,0 @@ -NULL = - -EXTRA_DIST = \ - autogen.sh \ - ac_define_dir.m4 \ - $(NULL) - -MAINTAINERCLEANFILES = \ - $(srcdir)/INSTALL \ - $(srcdir)/aclocal.m4 \ - $(srcdir)/autoscan.log \ - $(srcdir)/compile \ - $(srcdir)/config.guess \ - $(srcdir)/config.h.in \ - $(srcdir)/config.sub \ - $(srcdir)/configure.scan \ - $(srcdir)/depcomp \ - $(srcdir)/install-sh \ - $(srcdir)/ltmain.sh \ - $(srcdir)/missing \ - $(srcdir)/mkinstalldirs \ - $(srcdir)/ChangeLog \ - `find "$(srcdir)" -type f -name Makefile.in -print` - -SUBDIRS = src demo - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = glyphy.pc - --include $(top_srcdir)/git.mk diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 842f1b1..0000000 --- a/autogen.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# Run this to generate all the initial makefiles, etc. - -test -n "$srcdir" || srcdir=`dirname "$0"` -test -n "$srcdir" || srcdir=. - -olddir=`pwd` -cd $srcdir - -echo -n "checking for pkg-config... " -which pkg-config || { - echo "*** No pkg-config found, please install it ***" - exit 1 -} - -echo -n "checking for autoreconf... " -which autoreconf || { - echo "*** No autoreconf found, please install it ***" - exit 1 -} - -touch ChangeLog - -echo "running autoreconf --force --install --verbose" -autoreconf --force --install --verbose || exit $? - -cd $olddir -echo "running configure $@" -test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 982d0ef..0000000 --- a/configure.ac +++ /dev/null @@ -1,107 +0,0 @@ -AC_PREREQ([2.64]) -AC_INIT([glyphy], - [0.2.0], - [http://code.google.com/p/glyphy/issues/list], - [glyphy], - [http://code.google.com/p/glyphy/]) - -AC_CONFIG_SRCDIR([glyphy.pc.in]) -AC_CONFIG_HEADERS([config.h]) - -AM_INIT_AUTOMAKE([1.11.1 foreign dist-bzip2 no-dist-gzip -Wall]) -AM_SILENT_RULES([yes]) - -AC_CANONICAL_HOST - -android=false -case $host in - *-android*) android=true;; -esac -AM_CONDITIONAL(ANDROID, $android) - -# Initialize libtool -LT_PREREQ([2.2]) -LT_INIT([disable-static]) - -# TODO flesh this out -GLYPHY_LIBTOOL_VERSION_INFO="0:0:0" -AC_SUBST(GLYPHY_LIBTOOL_VERSION_INFO) - -# Check for programs -AC_PROG_CC -AM_PROG_CC_C_O -AC_PROG_CXX -PKG_PROG_PKG_CONFIG - -dnl ========================================================================== - -dnl syntax: pkgname, symbol, framework -AC_DEFUN([GLYPHY_CHECK_PACKAGE], -[ - m4_define([pkg], [$1]) - m4_define([_symbol], [$2]) - m4_define([_framework], [$3]) - m4_define([_more], [$4]) - m4_define([PKG], [m4_translit(pkg,[-a-z.+],[_A-Z_])]) - - HAVE_[]PKG=false - - if test -n "_framework"; then - case "$host" in - *-darwin*) - AC_MSG_CHECKING([for ]PKG) - HAVE_[]PKG=true - PKG[]_LIBS="-framework _framework" - AC_MSG_RESULT($PKG[]_LIBS) - break; - ;; - esac - fi - - if ! $HAVE_[]PKG; then - PKG_CHECK_MODULES(PKG, pkg, [HAVE_]PKG[=true], [HAVE_]PKG[=false]) - fi - - m4_foreach_w(lib, [m4_translit(PKG,[A-Z],[a-z]) PKG _more], [ - if ! $HAVE_[]PKG; then - AC_CHECK_LIB(lib, _symbol, [ - HAVE_]PKG[=true - ]PKG[_LIBS=-l]lib[ - ]) - fi - ]) - if [$HAVE_]PKG; then AC_DEFINE([HAVE_]PKG, 1, [Have ]pkg[ library]) fi - AM_CONDITIONAL([HAVE_]PKG, [$HAVE_]PKG) -]) - -GLYPHY_CHECK_PACKAGE(freetype2, FT_Init_FreeType) - -PKG_CHECK_MODULES(HARFBUZZ, harfbuzz >= 4.0.0, HAVE_HARFBUZZ=true, HAVE_HARFBUZZ=false) -AM_CONDITIONAL(HAVE_HARFBUZZ, $HAVE_HARFBUZZ) - -if $android; then - GLYPHY_CHECK_PACKAGE(gl, glCreateShader,, GLESv2) - GLYPHY_CHECK_PACKAGE(glew, glewInit) - GLYPHY_CHECK_PACKAGE(glut, glutInit,, freeglut-gles2) - GLUT_CFLAGS=-DFREEGLUT_GLES2 - AC_SUBST(GLUT_CFLAGS) - AC_DEFINE([__ANDROID__], 1, [On Android]) -else - GLYPHY_CHECK_PACKAGE(gl, glCreateShader, OpenGL) - GLYPHY_CHECK_PACKAGE(glew, glewInit) - GLYPHY_CHECK_PACKAGE(glut, glutInit, GLUT) -fi - -dnl =========================================================================== - -AC_DEFINE_DIR([PKGDATADIR], [$pkgdatadir], [Define to the directory containing package data.]) - -AC_CONFIG_FILES([ -glyphy.pc -Makefile -src/Makefile -demo/Makefile -demo/android/Makefile -]) - -AC_OUTPUT diff --git a/demo/Makefile.am b/demo/Makefile.am deleted file mode 100644 index 5ae517c..0000000 --- a/demo/Makefile.am +++ /dev/null @@ -1,88 +0,0 @@ -NULL = -SUBDIRS = -EXTRA_DIST = -CLEANFILES = -DISTCLEANFILES = -MAINTAINERCLEANFILES = -BUILT_SOURCES = -noinst_PROGRAMS = - -BUILT_SOURCES += default-text.h default-font.h -EXTRA_DIST += default-text.txt default-font.ttf -default-text.h: default-text.txt $(top_srcdir)/src/stringize - $(AM_V_GEN) $(top_srcdir)/src/stringize "static const char default_text[]" $< > $@ -default-font.h: default-font.ttf - $(AM_V_GEN) { \ - echo "static const unsigned char default_font[] = {"; \ - hexdump -v -e '"x" 1/1 "%02X" " "' < $< | fmt | sed 's/ *x/\\x/g;s/^/"/;s/$$/"/'; \ - echo '};'; \ - } > $@ - -if HAVE_HARFBUZZ -if HAVE_GL -if HAVE_GLUT - -if ANDROID -SUBDIRS += android -else -noinst_PROGRAMS += glyphy-demo -glyphy_demo_CPPFLAGS = \ - -I $(top_srcdir)/src \ - $(HARFBUZZ_CFLAGS) \ - $(GL_CFLAGS) \ - $(GLEW_CFLAGS) \ - $(GLUT_CFLAGS) \ - $(NULL) -glyphy_demo_LDADD = \ - $(top_builddir)/src/libglyphy.la \ - -lm \ - $(HARFBUZZ_LIBS) \ - $(GL_LIBS) \ - $(GLEW_LIBS) \ - $(GLUT_LIBS) \ - $(NULL) -glyphy_demo_SOURCES = \ - default-font.h \ - default-text.h \ - demo-atlas.h \ - demo-atlas.cc \ - demo-buffer.h \ - demo-buffer.cc \ - demo-common.h \ - demo-font.h \ - demo-font.cc \ - demo-glstate.h \ - demo-glstate.cc \ - demo-shader.h \ - demo-shader.cc \ - demo-view.h \ - demo-view.cc \ - glyphy-demo.cc \ - matrix4x4.h \ - matrix4x4.c \ - trackball.h \ - trackball.c \ - $(SHADERHEADERS) \ - $(NULL) -endif -SHADERS = \ - demo-fshader.glsl \ - demo-vshader.glsl \ - $(NULL) -SHADERHEADERS = $(patsubst %.glsl,%-glsl.h, $(SHADERS)) -BUILT_SOURCES += $(SHADERHEADERS) -EXTRA_DIST += $(SHADERS) - -%-glsl.h: %.glsl $(top_srcdir)/src/stringize - $(AM_V_GEN) $(top_srcdir)/src/stringize "static const char *`echo "$<" | \ - sed 's@.*/@@;s/[-.]/_/g'`" "$<" > "$@.tmp" && \ - mv "$@.tmp" "$@" || ($(RM) "$@.tmp"; false) - -endif -endif -endif - - - - --include $(top_srcdir)/git.mk diff --git a/demo/android/Makefile.am b/demo/android/Makefile.am deleted file mode 100644 index f584851..0000000 --- a/demo/android/Makefile.am +++ /dev/null @@ -1,15 +0,0 @@ -if ANDROID - -all: - NDK_PROJECT_PATH=$(srcdir) ndk-build NDK_DEBUG=1 - ant -buildfile $(srcdir)/build.xml debug - -clean-local: - NDK_PROJECT_PATH=$(srcdir) ndk-build clean - ant -buildfile $(srcdir)/build.xml clean - -endif - -GITIGNOREFILES = bin libs obj - --include $(top_srcdir)/git.mk diff --git a/demo/default-text.h b/demo/default-text.h deleted file mode 100644 index b28e128..0000000 --- a/demo/default-text.h +++ /dev/null @@ -1,71 +0,0 @@ -static const char default_text[] = -"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tincidunt nibh quis semper malesuada. Mauris sit amet metus sed tellus gravida\n" -"maximus sit amet a enim. Morbi et lorem congue, aliquam lorem vitae, condimentum erat. Mauris aliquet, sapien consequat blandit mattis, velit ante\n" -"molestie mi, ac condimentum justo leo sed odio. Curabitur at suscipit quam. Ut ac convallis ante, at sollicitudin sapien. Nulla pellentesque felis\n" -"id mi blandit dictum. Phasellus ultrices, odio non volutpat tincidunt, neque quam tristique lacus, nec gravida nulla ante id risus. Nulla sit amet\n" -"bibendum lectus, sed bibendum lectus. Vivamus ultrices metus sit amet sapien posuere volutpat. Suspendisse luctus non mauris nec iaculis. Duis\n" -"mattis enim libero, ac malesuada tortor gravida tempor. Cras sagittis felis at sollicitudin fermentum. Duis et ipsum bibendum, viverra felis quis,\n" -"consectetur lacus. Donec vulputate risus imperdiet, tincidunt purus nec, vestibulum lorem. Morbi iaculis tincidunt rutrum. Duis sit amet nulla\n" -"ut lectus efficitur suscipit. Curabitur urna turpis, congue lacinia varius vitae, interdum vel dolor. Vestibulum sit amet suscipit arcu, sit amet\n" -"tincidunt ipsum. Maecenas feugiat ante vel fermentum viverra. Sed aliquam sem ac quam bibendum, sit amet fringilla augue pharetra. Morbi scelerisque\n" -"tempus purus, interdum tempor est pulvinar bibendum. Duis tincidunt dictum ante vel sodales. Fusce quis cursus metus. Pellentesque mi mauris,\n" -"tincidunt ut orci ut, interdum dapibus dolor. Aliquam blandit, nisl et rhoncus laoreet, tellus nulla blandit tellus, sit amet cursus magna enim nec\n" -"ante. Integer venenatis a est sed hendrerit. Proin id porttitor turpis, aliquam tempus ex. Morbi tristique, felis ut aliquet luctus, orci tortor\n" -"sodales sem, vel imperdiet justo tortor sit amet arcu. Quisque ipsum sem, lacinia in fermentum eu, maximus in lectus. Pellentesque quis scelerisque\n" -"neque, eget ultricies ligula. Etiam accumsan orci mi, ut blandit nibh viverra quis. Pellentesque tincidunt auctor dictum. Integer tincidunt neque\n" -"at enim ultrices, in accumsan augue elementum. Sed placerat eros leo, ac venenatis metus dignissim vel. Suspendisse mauris sem, pulvinar id purus\n" -"non, hendrerit pretium sem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla accumsan massa id nulla\n" -"viverra semper. Aliquam sit amet neque vitae diam rhoncus feugiat. Etiam tellus purus, rhoncus id enim a, fermentum volutpat neque. Vestibulum\n" -"tortor augue, dapibus at varius sed, varius at nisl. Ut pharetra elementum varius. Nulla molestie, urna sit amet euismod venenatis, nunc magna\n" -"bibendum augue, vitae elementum augue elit eget urna. Morbi a leo at sapien tempor varius sit amet venenatis metus. Sed ac pellentesque turpis,\n" -"quis malesuada ligula. Cras quis ex mattis, ultricies nibh ac, vestibulum velit. Sed sed sagittis libero, at consectetur massa. Cras quis risus\n" -"nunc. Curabitur vitae odio laoreet, sodales sem eu, fermentum risus. Quisque laoreet felis eu mattis laoreet. Phasellus auctor velit ut varius\n" -"accumsan. Sed molestie bibendum mi varius auctor. Vestibulum iaculis, lacus ut mattis convallis, turpis purus cursus nibh, nec ullamcorper dolor nibh\n" -"eget nunc. Nam lacinia justo ac vestibulum lacinia. Nulla vestibulum eu urna a sodales. Aliquam blandit congue lacus vitae ornare. Nulla bibendum\n" -"vehicula tortor eu vestibulum. Mauris nec pretium est. Pellentesque faucibus quam et est sollicitudin, quis condimentum justo placerat. Etiam urna\n" -"elit, porttitor sodales dui congue, maximus sodales dolor. Proin lacinia diam quis libero molestie viverra. Pellentesque tortor mauris, sodales\n" -"vel luctus non, euismod eu risus. Nulla sed condimentum ex. Morbi ac mi quis felis laoreet mattis. Fusce nec quam orci. Morbi pretium quis risus\n" -"a tincidunt. Phasellus accumsan suscipit nisi sed viverra. Vestibulum eu vulputate lorem. Nullam fermentum turpis eget nulla rutrum egestas. Sed\n" -"tortor nisi, consectetur a semper ut, lacinia in lectus. Phasellus finibus at diam nec interdum. Nunc mollis congue enim, et tempor neque dapibus\n" -"quis. Nunc a egestas quam, vitae tempor mi. Sed at lobortis neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames\n" -"ac turpis egestas. Etiam blandit mi sit amet ipsum accumsan, eget euismod nulla facilisis. Morbi imperdiet ex massa, in porttitor nibh posuere\n" -"ut. Curabitur bibendum lorem id ultricies placerat. Aenean tincidunt malesuada mauris, sit amet rhoncus odio ornare ac. Morbi venenatis turpis\n" -"non nulla tincidunt, bibendum tincidunt quam ultricies. Donec dictum, lorem eget commodo malesuada, risus nisi ornare augue, feugiat accumsan\n" -"dui massa eu ipsum. Mauris aliquam sapien id felis faucibus ornare. Nullam eleifend felis vel metus rutrum mollis. Quisque posuere eros vitae\n" -"imperdiet dapibus. Suspendisse quis est laoreet, faucibus lectus at, mattis est. Vestibulum nec neque ut ante auctor luctus. Nunc posuere vulputate\n" -"egestas. Quisque sit amet sodales erat, ac suscipit lorem. Ut auctor dapibus efficitur. Phasellus tincidunt cursus neque in ultrices. Sed pretium\n" -"viverra felis vitae aliquet. Vivamus at quam libero. Phasellus mattis orci ut egestas finibus. Suspendisse mattis maximus consequat. Ut id aliquam\n" -"lectus. Maecenas ut libero in nulla tempus accumsan at at tellus. Quisque a nibh quis ligula pharetra vehicula id et ante. Pellentesque et nisl in\n" -"lectus commodo sollicitudin non quis lectus. Vestibulum varius leo at diam fringilla, nec vulputate diam pellentesque. Curabitur id nisl in nibh\n" -"sollicitudin vestibulum non sit amet odio. Nunc pulvinar lectus ac est sagittis mollis. Nunc vel consectetur nulla, sit amet eleifend elit. Etiam\n" -"volutpat, ligula id cursus posuere, est lacus sagittis purus, scelerisque tempus sem lectus tempor nisi. Curabitur scelerisque leo et neque\n" -"ultricies semper. Sed in efficitur urna. Aliquam tempus cursus tortor. Sed feugiat mi elit, id ullamcorper nulla ullamcorper vitae. Suspendisse\n" -"rutrum ornare orci, eget facilisis nisi ornare et. Aenean sed leo vitae lorem congue lobortis. Nunc vel dui condimentum, sagittis sapien ac,\n" -"ultricies odio. Suspendisse elementum iaculis commodo. Phasellus pellentesque purus nec nisl pretium eleifend. Suspendisse a imperdiet urna. Fusce\n" -"eleifend et nunc at elementum. Morbi imperdiet varius mattis. Cras pharetra urna nulla, gravida sodales orci blandit et. Nulla dignissim eleifend\n" -"tempus. Vivamus placerat tristique purus et facilisis. In eget maximus urna. Mauris ut commodo elit. Aliquam erat volutpat. Donec efficitur augue\n" -"vitae nisi suscipit, vel aliquet sem posuere. Ut placerat ullamcorper lorem, eget consequat nunc ultricies nec. Nulla scelerisque ex non velit\n" -"efficitur imperdiet. Aliquam faucibus augue vel rhoncus feugiat. Aenean hendrerit nunc sem, ac hendrerit tortor fringilla sed. Morbi lorem massa,\n" -"dictum at maximus vitae, dictum et mi. Nullam ante elit, ultrices tincidunt tempor quis, aliquam ut lacus. Donec ut aliquet purus. Sed dolor mi,\n" -"rhoncus auctor tristique ac, lobortis non urna. Sed tempor leo ut finibus mattis. Aenean fermentum augue eget nisi aliquet feugiat. Donec convallis\n" -"laoreet nulla id malesuada. Vestibulum in congue enim, non sagittis massa. Ut et euismod nibh, eu euismod leo. Class aptent taciti sociosqu ad litora\n" -"torquent per conubia nostra, per inceptos himenaeos. Cras sodales purus nec ligula egestas cursus. Sed gravida, elit non eleifend semper, ex erat\n" -"ultricies augue, nec suscipit velit metus in augue. Nulla dapibus, ante a aliquam cursus, mi purus lobortis purus, sit amet feugiat neque sapien\n" -"at mauris. Vivamus mattis sagittis purus, quis viverra dolor mollis nec. Pellentesque habitant morbi tristique senectus et netus et malesuada fames\n" -"ac turpis egestas. Fusce in semper justo, eget mattis magna. Donec libero augue, aliquam eget mi at, laoreet sodales metus. Etiam gravida, libero\n" -"eu pellentesque imperdiet, nulla dolor scelerisque tortor, vitae placerat est risus non metus. Etiam vel laoreet est. Ut posuere turpis facilisis,\n" -"faucibus magna nec, ornare augue. Pellentesque auctor feugiat ornare. Etiam vulputate vitae odio et lacinia. Aenean semper purus vitae erat tincidunt\n" -"volutpat. Sed et laoreet nulla, eu condimentum arcu. Maecenas magna arcu, condimentum ut elementum vitae, laoreet sed justo. Maecenas bibendum\n" -"lacus vel blandit tincidunt. Praesent mattis, eros eget auctor malesuada, felis ligula egestas eros, at lacinia dolor lacus sed arcu. Praesent\n" -"lectus lacus, rhoncus ultricies facilisis vel, tristique pellentesque eros. Donec et justo placerat, finibus lectus id, mattis augue. Nam\n" -"consequat, felis a commodo varius, odio erat viverra urna, mollis sodales nibh lectus non turpis. Maecenas quis egestas risus, eget lobortis\n" -"dui. Sed pellentesque vitae neque non condimentum. Proin eu maximus magna, ac posuere libero. Maecenas porta interdum metus eu hendrerit. Integer\n" -"ut blandit risus. Maecenas facilisis consectetur dolor a mattis. Morbi elementum tincidunt turpis, vel tempus est mattis vel. Morbi et ultrices\n" -"velit, at dapibus neque. Vestibulum dictum in mi et rhoncus. In congue, elit vel ultricies maximus, arcu augue venenatis neque, sed imperdiet purus\n" -"leo et risus. Quisque tincidunt sapien felis, vitae elementum erat aliquet in. Curabitur mauris nibh, dictum sollicitudin dolor ut, accumsan luctus\n" -"nulla. Aenean quis diam quam. Etiam viverra odio at felis tincidunt laoreet. Proin euismod non lectus sit amet lacinia. Proin aliquet mauris et dui\n" -"consectetur sagittis. Ut tempus fringilla odio, vitae scelerisque neque efficitur ut. Quisque a nulla pulvinar, varius dolor sed, eleifend magna.\n" -"Vivamus eu neque pulvinar, accumsan odio venenatis, mollis leo. Morbi aliquet ac dolor sed lobortis. Maecenas urna sapien, volutpat non sagittis in,\n" -"tempor eget enim. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed mollis porta lectus, id lobortis metus elementum vitae. Nullam\n" -"non nulla sed risus rhoncus tempor. Nullam id quam hendrerit, dictum purus ut, maximus ipsum. Cras auct\n" -; diff --git a/demo/demo-atlas-glsl.h b/demo/demo-atlas-glsl.h deleted file mode 100644 index 9b93c2a..0000000 --- a/demo/demo-atlas-glsl.h +++ /dev/null @@ -1,18 +0,0 @@ -static const char *demo_atlas_glsl = -"uniform sampler2D u_atlas_tex;\n" -"uniform ivec4 u_atlas_info;\n" -"\n" -"#define GLYPHY_TEXTURE1D_EXTRA_DECLS , sampler2D _tex, ivec4 _atlas_info, ivec2 _atlas_pos\n" -"#define GLYPHY_TEXTURE1D_EXTRA_ARGS , _tex, _atlas_info, _atlas_pos\n" -"#define GLYPHY_DEMO_EXTRA_ARGS , u_atlas_tex, u_atlas_info, gi.atlas_pos\n" -"\n" -"vec4\n" -"glyphy_texture1D_func (int offset GLYPHY_TEXTURE1D_EXTRA_DECLS)\n" -"{\n" -" ivec2 item_geom = _atlas_info.zw;\n" -" vec2 pos = (vec2 (_atlas_pos.xy * item_geom +\n" -" ivec2 (mod (float (offset), float (item_geom.x)), offset / item_geom.x)) +\n" -" + vec2 (.5, .5)) / vec2(_atlas_info.xy);\n" -" return texture2D (_tex, pos);\n" -"}\n" -; diff --git a/git.mk b/git.mk deleted file mode 100644 index 315d06b..0000000 --- a/git.mk +++ /dev/null @@ -1,285 +0,0 @@ -# git.mk -# -# Copyright 2009, Red Hat, Inc. -# Copyright 2010,2011,2012,2013 Behdad Esfahbod -# Written by Behdad Esfahbod -# -# Copying and distribution of this file, with or without modification, -# is permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. -# -# The latest version of this file can be downloaded from: -# https://raw.github.com/behdad/git.mk/master/git.mk -# Bugs, etc, should be reported upstream at: -# https://github.com/behdad/git.mk -# -# To use in your project, import this file in your git repo's toplevel, -# then do "make -f git.mk". This modifies all Makefile.am files in -# your project to -include git.mk. Remember to add that line to new -# Makefile.am files you create in your project, or just rerun the -# "make -f git.mk". -# -# This enables automatic .gitignore generation. If you need to ignore -# more files, add them to the GITIGNOREFILES variable in your Makefile.am. -# But think twice before doing that. If a file has to be in .gitignore, -# chances are very high that it's a generated file and should be in one -# of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES. -# -# The only case that you need to manually add a file to GITIGNOREFILES is -# when remove files in one of mostlyclean-local, clean-local, distclean-local, -# or maintainer-clean-local make targets. -# -# Note that for files like editor backup, etc, there are better places to -# ignore them. See "man gitignore". -# -# If "make maintainer-clean" removes the files but they are not recognized -# by this script (that is, if "git status" shows untracked files still), send -# me the output of "git status" as well as your Makefile.am and Makefile for -# the directories involved and I'll diagnose. -# -# For a list of toplevel files that should be in MAINTAINERCLEANFILES, see -# Makefile.am.sample in the git.mk git repo. -# -# Don't EXTRA_DIST this file. It is supposed to only live in git clones, -# not tarballs. It serves no useful purpose in tarballs and clutters the -# build dir. -# -# This file knows how to handle autoconf, automake, libtool, gtk-doc, -# gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu. -# -# This makefile provides the following targets: -# -# - all: "make all" will build all gitignore files. -# - gitignore: makes all gitignore files in the current dir and subdirs. -# - .gitignore: make gitignore file for the current dir. -# - gitignore-recurse: makes all gitignore files in the subdirs. -# -# KNOWN ISSUES: -# -# - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the -# submodule doesn't find us. If you have configure.{in,ac} files in -# subdirs, add a proxy git.mk file in those dirs that simply does: -# "include $(top_srcdir)/../git.mk". Add more ..'s to your taste. -# And add those files to git. See vte/gnome-pty-helper/git.mk for -# example. -# - - - -############################################################################### -# Variables user modules may want to add to toplevel MAINTAINERCLEANFILES: -############################################################################### - -# -# Most autotools-using modules should be fine including this variable in their -# toplevel MAINTAINERCLEANFILES: -GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \ - $(srcdir)/aclocal.m4 \ - $(srcdir)/ar-lib \ - $(srcdir)/autoscan.log \ - $(srcdir)/compile \ - $(srcdir)/config.guess \ - $(srcdir)/config.h.in \ - $(srcdir)/config.sub \ - $(srcdir)/configure.scan \ - $(srcdir)/depcomp \ - $(srcdir)/install-sh \ - $(srcdir)/ltmain.sh \ - $(srcdir)/missing \ - $(srcdir)/mkinstalldirs -# -# All modules should also be fine including the following variable, which -# removes automake-generated Makefile.in files: -GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \ - `$(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' $(srcdir)/configure.ac | \ - while read f; do \ - case $$f in Makefile|*/Makefile) \ - test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \ - done` -# -# Modules that use libtool /and/ use AC_CONFIG_MACRO_DIR([m4]) may also -# include this: -GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \ - $(srcdir)/m4/libtool.m4 \ - $(srcdir)/m4/ltoptions.m4 \ - $(srcdir)/m4/ltsugar.m4 \ - $(srcdir)/m4/ltversion.m4 \ - $(srcdir)/m4/lt~obsolete.m4 - - - -############################################################################### -# Default rule is to install ourselves in all Makefile.am files: -############################################################################### - -git-all: git-mk-install - -git-mk-install: - @echo "Installing git makefile" - @any_failed=; \ - find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \ - if grep 'include .*/git.mk' $$x >/dev/null; then \ - echo "$$x already includes git.mk"; \ - else \ - failed=; \ - echo "Updating $$x"; \ - { cat $$x; \ - echo ''; \ - echo '-include $$(top_srcdir)/git.mk'; \ - } > $$x.tmp || failed=1; \ - if test x$$failed = x; then \ - mv $$x.tmp $$x || failed=1; \ - fi; \ - if test x$$failed = x; then : else \ - echo "Failed updating $$x"; >&2 \ - any_failed=1; \ - fi; \ - fi; done; test -z "$$any_failed" - -.PHONY: git-all git-mk-install - - - -############################################################################### -# Actual .gitignore generation: -############################################################################### - -$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk - $(AM_V_GEN) \ - { \ - if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \ - for x in \ - $(DOC_MODULE)-decl-list.txt \ - $(DOC_MODULE)-decl.txt \ - tmpl/$(DOC_MODULE)-unused.sgml \ - "tmpl/*.bak" \ - xml html \ - ; do echo "/$$x"; done; \ - fi; \ - if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \ - for lc in $(DOC_LINGUAS); do \ - for x in \ - $(if $(DOC_MODULE),$(DOC_MODULE).xml) \ - $(DOC_PAGES) \ - $(DOC_INCLUDES) \ - ; do echo "/$$lc/$$x"; done; \ - done; \ - for x in \ - $(_DOC_OMF_ALL) \ - $(_DOC_DSK_ALL) \ - $(_DOC_HTML_ALL) \ - $(_DOC_MOFILES) \ - $(DOC_H_FILE) \ - "*/.xml2po.mo" \ - "*/*.omf.out" \ - ; do echo /$$x; done; \ - fi; \ - if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \ - for lc in $(HELP_LINGUAS); do \ - for x in \ - $(HELP_FILES) \ - "$$lc.stamp" \ - "$$lc.mo" \ - ; do echo "/$$lc/$$x"; done; \ - done; \ - fi; \ - if test "x$(gsettings_SCHEMAS)" = x; then :; else \ - for x in \ - $(gsettings_SCHEMAS:.xml=.valid) \ - $(gsettings__enum_file) \ - ; do echo "/$$x"; done; \ - fi; \ - if test -f $(srcdir)/po/Makefile.in.in; then \ - for x in \ - po/Makefile.in.in \ - po/Makefile.in \ - po/Makefile \ - po/POTFILES \ - po/stamp-it \ - po/.intltool-merge-cache \ - "po/*.gmo" \ - "po/*.mo" \ - po/$(GETTEXT_PACKAGE).pot \ - intltool-extract.in \ - intltool-merge.in \ - intltool-update.in \ - ; do echo "/$$x"; done; \ - fi; \ - if test -f $(srcdir)/configure; then \ - for x in \ - autom4te.cache \ - configure \ - config.h \ - stamp-h1 \ - libtool \ - config.lt \ - ; do echo "/$$x"; done; \ - fi; \ - if test "x$(DEJATOOL)" = x; then :; else \ - for x in \ - $(DEJATOOL) \ - ; do echo "/$$x.sum"; echo "/$$x.log"; done; \ - echo /site.exp; \ - fi; \ - if test "x$(am__dirstamp)" = x; then :; else \ - echo "$(am__dirstamp)"; \ - fi; \ - if test "x$(LTCOMPILE)" = x; then :; else \ - for x in \ - "*.lo" \ - ".libs" "_libs" \ - ; do echo "$$x"; done; \ - fi; \ - for x in \ - .gitignore \ - $(GITIGNOREFILES) \ - $(CLEANFILES) \ - $(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \ - $(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \ - $(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \ - so_locations \ - $(MOSTLYCLEANFILES) \ - "*.$(OBJEXT)" \ - $(DISTCLEANFILES) \ - $(am__CONFIG_DISTCLEAN_FILES) \ - $(CONFIG_CLEAN_FILES) \ - TAGS ID GTAGS GRTAGS GSYMS GPATH tags \ - "*.tab.c" \ - $(MAINTAINERCLEANFILES) \ - $(BUILT_SOURCES) \ - $(DEPDIR) \ - Makefile \ - Makefile.in \ - "*.orig" \ - "*.rej" \ - "*.bak" \ - "*~" \ - ".*.sw[nop]" \ - ".dirstamp" \ - ; do echo "/$$x"; done; \ - } | \ - sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \ - sed 's@/[.]/@/@g' | \ - LC_ALL=C sort | uniq > $@.tmp && \ - mv $@.tmp $@; - -all: $(srcdir)/.gitignore gitignore-recurse-maybe -gitignore: $(srcdir)/.gitignore gitignore-recurse - -gitignore-recurse-maybe: - @for subdir in $(DIST_SUBDIRS); do \ - case " $(SUBDIRS) " in \ - *" $$subdir "*) :;; \ - *) test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) .gitignore gitignore-recurse-maybe || echo "Skipping $$subdir");; \ - esac; \ - done -gitignore-recurse: - @for subdir in $(DIST_SUBDIRS); do \ - test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) .gitignore gitignore-recurse || echo "Skipping $$subdir"); \ - done - -maintainer-clean: gitignore-clean -gitignore-clean: - -rm -f $(srcdir)/.gitignore - -.PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index d98acb7..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,51 +0,0 @@ -NULL = -EXTRA_DIST = -CLEANFILES = -DISTCLEANFILES = -MAINTAINERCLEANFILES = -BUILT_SOURCES = - -lib_LTLIBRARIES = libglyphy.la -libglyphy_la_CPPFLAGS = \ - $(NULL) -libglyphy_la_LIBADD = \ - -lm \ - $(NULL) -libglyphy_la_LDFLAGS = \ - -version-info @GLYPHY_LIBTOOL_VERSION_INFO@ \ - $(NULL) -libglyphy_la_SOURCES = \ - glyphy-curves.cc \ - glyphy-encode.cc \ - glyphy-extents.cc \ - glyphy-shaders.cc \ - $(PUBLICHEADERS) \ - $(SHADERHEADERS) \ - $(NULL) -PUBLICHEADERS = \ - glyphy.h \ - glyphy-freetype.h \ - glyphy-harfbuzz.h \ - $(NULL) -SHADERS = \ - glyphy-slug.glsl \ - $(NULL) -SHADERHEADERS = $(patsubst %.glsl,%-glsl.h, $(SHADERS)) -BUILT_SOURCES += $(SHADERHEADERS) -EXTRA_DIST += $(SHADERS) - -pkginclude_HEADERS = \ - $(PUBLICHEADERS) \ - $(NULL) -pkgdata_DATA = \ - $(SHADERS) \ - $(NULL) - -EXTRA_DIST += stringize -%-glsl.h: %.glsl stringize - $(AM_V_GEN) $(srcdir)/stringize "static const char *`echo "$<" | \ - sed 's@.*/@@;s/[-.]/_/g'`" "$<" > "$@.tmp" && \ - mv "$@.tmp" "$@" || ($(RM) "$@.tmp"; false) - - --include $(top_srcdir)/git.mk From 7888f9ad75a78cac7634f0ac71458aa228f073a9 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:15:31 -0600 Subject: [PATCH 18/68] Keep extents and advance in font units, drop normalization round-trip The encoder outputs extents in font design units. Previously the demo divided by upem then the shader multiplied back. Now everything stays in font units. Screen positions use font_size/upem as the scale factor. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-buffer.cc | 2 +- demo/demo-font.cc | 5 +---- demo/demo-shader.cc | 21 +++++++++------------ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index 2a59fb0..a6267d6 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -135,7 +135,7 @@ demo_buffer_add_text (demo_buffer_t *buffer, corner.x = buffer->cursor.x; corner.y = buffer->cursor.y - font_size; glyphy_extents_add (&buffer->logical_extents, &corner); - corner.x = buffer->cursor.x + font_size * gi.advance; + corner.x = buffer->cursor.x + scale * gi.advance; corner.y = buffer->cursor.y; glyphy_extents_add (&buffer->logical_extents, &corner); diff --git a/demo/demo-font.cc b/demo/demo-font.cc index a9b7500..f8f28a4 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -126,10 +126,7 @@ encode_glyph (demo_font_t *font, extents)) die ("Failed encoding blob"); - unsigned int upem = hb_face_get_upem (font->face); - glyphy_extents_scale (extents, 1. / upem, 1. / upem); - - *advance = hb_font_get_glyph_h_advance (font->font, glyph_index) / (double) upem; + *advance = hb_font_get_glyph_h_advance (font->font, glyph_index); font->num_glyphs++; font->sum_curves += glyphy_curve_accumulator_get_num_curves (font->acc); diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 0db23e5..6eab524 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -28,12 +28,9 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, if (gi->is_empty) return; - /* Texcoords in font design units (extents are normalized, scale back) */ - double upem = gi->upem; - double min_x = gi->extents.min_x * upem; - double max_x = gi->extents.max_x * upem; - double min_y = gi->extents.min_y * upem; - double max_y = gi->extents.max_y * upem; + /* Extents and texcoords are in font design units. + * Screen position uses font_size / upem as the scale. */ + double scale = font_size / gi->upem; glyph_vertex_t v[4]; @@ -41,13 +38,13 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, int cx = (ci >> 1) & 1; int cy = ci & 1; - double vx = p.x + font_size * ((1 - cx) * gi->extents.min_x + cx * gi->extents.max_x); - double vy = p.y - font_size * ((1 - cy) * gi->extents.min_y + cy * gi->extents.max_y); + double ex = (1 - cx) * gi->extents.min_x + cx * gi->extents.max_x; + double ey = (1 - cy) * gi->extents.min_y + cy * gi->extents.max_y; - v[ci].x = (float) vx; - v[ci].y = (float) vy; - v[ci].tx = (float) ((1 - cx) * min_x + cx * max_x); - v[ci].ty = (float) ((1 - cy) * min_y + cy * max_y); + v[ci].x = (float) (p.x + scale * ex); + v[ci].y = (float) (p.y - scale * ey); + v[ci].tx = (float) ex; + v[ci].ty = (float) ey; v[ci].atlas_offset = gi->atlas_offset; } From 64408061b12e537dc161ab9c254f0814cd039ec3 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:20:21 -0600 Subject: [PATCH 19/68] Share endpoints between adjacent curves in contour Adjacent curves in a contour share p3/p1. Instead of storing 2 texels per curve (2N total), store N+1 texels per contour: (p1,p2) (p3/p1,p2_next) ... (p3,0) The shader reads curveLoc and curveLoc+1, unchanged. Band indices point to each curve's first texel. Nearly halves curve data storage. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 87 ++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index f8fd5d5..f7aad32 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -192,8 +192,21 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, for (auto &band : vband_curves) total_curve_indices += band.size (); unsigned int header_len = 2; /* blob header: extents + band counts */ + /* Compute curve data size with shared endpoints. + * Adjacent curves in a contour share p3/p1: N+1 texels per contour. + * Layout per contour: (p1,p2) (p3/p1,p2) ... (p3,0) + * The shader reads curveLoc and curveLoc+1, same as before. */ + unsigned int num_contour_breaks = 0; + for (unsigned int i = 0; i + 1 < num_curves; i++) + if (curves[i].p3.x != curves[i + 1].p1.x || + curves[i].p3.y != curves[i + 1].p1.y) + num_contour_breaks++; + + /* With sharing: num_curves + (num_contour_breaks + 1) texels + * (one extra texel per contour for the final p3). */ + unsigned int curve_data_len = num_curves + num_contour_breaks + 1; + unsigned int band_headers_len = num_hbands + num_vbands; - unsigned int curve_data_len = num_curves * 2; unsigned int total_len = header_len + band_headers_len + total_curve_indices + curve_data_len; if (total_len > blob_size) @@ -201,9 +214,7 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int curve_data_offset = header_len + band_headers_len + total_curve_indices; - /* Pack blob header: - * Texel 0: (min_x, min_y, max_x, max_y) - quantized extents - * Texel 1: (num_hbands, num_vbands, 0, 0) */ + /* Pack blob header */ blob[0].r = quantize (extents->min_x); blob[0].g = quantize (extents->min_y); blob[0].b = quantize (extents->max_x); @@ -213,44 +224,48 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, blob[1].b = 0; blob[1].a = 0; - /* Pack curve data. - * Quantize p1 and p3 first, then compute p2 as the average of the - * quantized p1 and p3 IF the curve is a line (p2 == midpoint(p1,p3)). - * This ensures a = p1 - 2*p2 + p3 = 0 exactly in the shader, - * preventing quantization from turning lines into slight curves. */ + /* Pack curve data with shared endpoints. + * Build curve_texel_offset[i] = texel offset for curve i's first texel. */ + std::vector curve_texel_offset (num_curves); + unsigned int texel = curve_data_offset; + for (unsigned int i = 0; i < num_curves; i++) { - unsigned int off = curve_data_offset + i * 2; - int16_t p1x = quantize (curves[i].p1.x); - int16_t p1y = quantize (curves[i].p1.y); - int16_t p3x = quantize (curves[i].p3.x); - int16_t p3y = quantize (curves[i].p3.y); - int16_t p2x, p2y; - - /* Check if p2 is midpoint of p1 and p3 (i.e., curve is a line) */ - double mid_x = (curves[i].p1.x + curves[i].p3.x) * 0.5; - double mid_y = (curves[i].p1.y + curves[i].p3.y) * 0.5; - if (curves[i].p2.x == mid_x && curves[i].p2.y == mid_y) { - /* Line: compute p2 from quantized endpoints to preserve a=0 */ - p2x = (int16_t) ((p1x + p3x) / 2); - p2y = (int16_t) ((p1y + p3y) / 2); + bool contour_start = (i == 0 || + curves[i - 1].p3.x != curves[i].p1.x || + curves[i - 1].p3.y != curves[i].p1.y); + + if (contour_start) { + curve_texel_offset[i] = texel; + /* First curve in contour: write (p1, p2) */ + blob[texel].r = quantize (curves[i].p1.x); + blob[texel].g = quantize (curves[i].p1.y); + blob[texel].b = quantize (curves[i].p2.x); + blob[texel].a = quantize (curves[i].p2.y); + texel++; } else { - p2x = quantize (curves[i].p2.x); - p2y = quantize (curves[i].p2.y); + /* Non-start curve: p12 is in the previous texel (p3_prev, p2) */ + curve_texel_offset[i] = texel - 1; } - blob[off].r = p1x; - blob[off].g = p1y; - blob[off].b = p2x; - blob[off].a = p2y; + /* Write (p3, p2_next) or (p3, 0) if last in contour */ + bool has_next = (i + 1 < num_curves && + curves[i].p3.x == curves[i + 1].p1.x && + curves[i].p3.y == curves[i + 1].p1.y); - blob[off + 1].r = p3x; - blob[off + 1].g = p3y; - blob[off + 1].b = 0; - blob[off + 1].a = 0; + blob[texel].r = quantize (curves[i].p3.x); + blob[texel].g = quantize (curves[i].p3.y); + if (has_next) { + blob[texel].b = quantize (curves[i + 1].p2.x); + blob[texel].a = quantize (curves[i + 1].p2.y); + } else { + blob[texel].b = 0; + blob[texel].a = 0; + } + texel++; } /* Pack band headers and curve indices. - * All offsets are relative to blob start (including header). */ + * All offsets are relative to blob start. */ unsigned int index_offset = header_len + band_headers_len; for (unsigned int b = 0; b < num_hbands; b++) { @@ -260,7 +275,7 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, blob[header_len + b].a = 0; for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { - unsigned int curve_off = curve_data_offset + hband_curves[b][ci] * 2; + unsigned int curve_off = curve_texel_offset[hband_curves[b][ci]]; blob[index_offset].r = (int16_t) curve_off; blob[index_offset].g = 0; blob[index_offset].b = 0; @@ -276,7 +291,7 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, blob[header_len + num_hbands + b].a = 0; for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { - unsigned int curve_off = curve_data_offset + vband_curves[b][ci] * 2; + unsigned int curve_off = curve_texel_offset[vband_curves[b][ci]]; blob[index_offset].r = (int16_t) curve_off; blob[index_offset].g = 0; blob[index_offset].b = 0; From 1dcbbc8f36234feaa9a8122ca53361c991e80cc7 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:24:19 -0600 Subject: [PATCH 20/68] Update CI: meson only, Ubuntu 24.04, drop harfbuzz build Ubuntu 24.04 has harfbuzz >= 4.0.0, no need to build from source. Remove autotools steps, autotools deps, and harfbuzz cache. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/linux-build.yml | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 8490f8c..b520bd9 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -8,32 +8,11 @@ on: jobs: build-test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v2 - - name: Cache Harfbuzz - uses: actions/cache@v3 - with: - path: harfbuzz-7.3.0 - key: ${{ runner.os }}-build + - uses: actions/checkout@v4 - name: Install build dependencies - run: sudo apt install -y curl gcc g++ libfreetype6-dev libglib2.0-dev libcairo2-dev make cmake automake autoconf libtool libharfbuzz-dev meson freeglut3-dev libglew-dev - - name: Download a recent harfbuzz archive - # TODO: Ubuntu 22.04 has harfbuzz < 4.0.0. Ubuntu 24.04 will have harfbuzz >= 4.0.0. - # The harfbuzz build will not be required when Github updates the runners. - run: curl -L https://github.com/harfbuzz/harfbuzz/releases/download/7.3.0/harfbuzz-7.3.0.tar.xz -O - - name: Decompress harfbuzz archive - run: tar xvf harfbuzz-7.3.0.tar.xz - - name: Configure harfbuzz build - run: cd harfbuzz-7.3.0; meson setup build - - name: Build harfbuzz - run: cd harfbuzz-7.3.0; meson compile -C build - - name: Install harfbuzz - run: cd harfbuzz-7.3.0; sudo meson install -C build - - name: Configure glyphy - run: ./autogen.sh - - name: Make glyphy - run: make + run: sudo apt install -y gcc g++ libfreetype6-dev libharfbuzz-dev meson freeglut3-dev libglew-dev - name: Meson setup run: meson setup build - name: Meson compile From 66d32d3689e1058c6876dce21f2e34915b857f82 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:34:29 -0600 Subject: [PATCH 21/68] Add symmetric band optimization For each band, store curves sorted in both directions: descending max (for rightward/upward ray) and ascending min (for leftward/ downward ray). A split value per band determines which direction to use based on pixel position. Pixels on the left side of the glyph use leftward ray with ascending sort; pixels on the right use rightward ray with descending sort. Each side exits early after processing only the nearby curves. Coverage formula adjusted for leftward ray: saturate(0.5 - r) instead of saturate(r + 0.5). ~15% speedup (1725 -> 1980 fps). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 100 +++++++++++++++++++++++++++++++++---------- src/glyphy-slug.glsl | 36 +++++++++++----- 2 files changed, 104 insertions(+), 32 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index f7aad32..637a2bf 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -63,12 +63,24 @@ quantize (double v) return (int16_t) round (v * GLYPHY_UNITS_PER_EM_UNIT); } +static double +curve_min_x (const glyphy_curve_t *c) +{ + return fmin (fmin (c->p1.x, c->p2.x), c->p3.x); +} + static double curve_max_x (const glyphy_curve_t *c) { return fmax (fmax (c->p1.x, c->p2.x), c->p3.x); } +static double +curve_min_y (const glyphy_curve_t *c) +{ + return fmin (fmin (c->p1.y, c->p2.y), c->p3.y); +} + static double curve_max_y (const glyphy_curve_t *c) { @@ -171,25 +183,40 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } } - /* Sort curves within bands for early exit. - * H-bands: descending max-x (rightward ray exits early). - * V-bands: descending max-y (upward ray exits early). */ - for (auto &band : hband_curves) - std::sort (band.begin (), band.end (), + /* Build two sort orders per band for symmetric optimization. + * Descending max: for rightward/upward ray (current behavior). + * Ascending min: for leftward/downward ray. */ + std::vector> hband_curves_asc (num_hbands); + std::vector> vband_curves_asc (num_vbands); + + for (unsigned int b = 0; b < num_hbands; b++) { + hband_curves_asc[b] = hband_curves[b]; + std::sort (hband_curves[b].begin (), hband_curves[b].end (), [&] (unsigned int a, unsigned int b) { return curve_max_x (&curves[a]) > curve_max_x (&curves[b]); }); + std::sort (hband_curves_asc[b].begin (), hband_curves_asc[b].end (), + [&] (unsigned int a, unsigned int b) { + return curve_min_x (&curves[a]) < curve_min_x (&curves[b]); + }); + } - for (auto &band : vband_curves) - std::sort (band.begin (), band.end (), + for (unsigned int b = 0; b < num_vbands; b++) { + vband_curves_asc[b] = vband_curves[b]; + std::sort (vband_curves[b].begin (), vband_curves[b].end (), [&] (unsigned int a, unsigned int b) { return curve_max_y (&curves[a]) > curve_max_y (&curves[b]); }); + std::sort (vband_curves_asc[b].begin (), vband_curves_asc[b].end (), + [&] (unsigned int a, unsigned int b) { + return curve_min_y (&curves[a]) < curve_min_y (&curves[b]); + }); + } - /* Compute sizes */ + /* Compute sizes -- two index lists per band */ unsigned int total_curve_indices = 0; - for (auto &band : hband_curves) total_curve_indices += band.size (); - for (auto &band : vband_curves) total_curve_indices += band.size (); + for (auto &band : hband_curves) total_curve_indices += band.size () * 2; + for (auto &band : vband_curves) total_curve_indices += band.size () * 2; unsigned int header_len = 2; /* blob header: extents + band counts */ /* Compute curve data size with shared endpoints. @@ -265,39 +292,68 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } /* Pack band headers and curve indices. + * Band header: (count, desc_offset, asc_offset, split_value) * All offsets are relative to blob start. */ unsigned int index_offset = header_len + band_headers_len; + /* Split value: midpoint of glyph extent */ + int16_t hband_split = quantize ((extents->min_x + extents->max_x) * 0.5); + int16_t vband_split = quantize ((extents->min_y + extents->max_y) * 0.5); + for (unsigned int b = 0; b < num_hbands; b++) { - blob[header_len + b].r = (int16_t) hband_curves[b].size (); - blob[header_len + b].g = (int16_t) index_offset; - blob[header_len + b].b = 0; - blob[header_len + b].a = 0; + unsigned int hdr = header_len + b; + unsigned int desc_off = index_offset; for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { - unsigned int curve_off = curve_texel_offset[hband_curves[b][ci]]; - blob[index_offset].r = (int16_t) curve_off; + blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves[b][ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; index_offset++; } + + unsigned int asc_off = index_offset; + + for (unsigned int ci = 0; ci < hband_curves_asc[b].size (); ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves_asc[b][ci]]; + blob[index_offset].g = 0; + blob[index_offset].b = 0; + blob[index_offset].a = 0; + index_offset++; + } + + blob[hdr].r = (int16_t) hband_curves[b].size (); + blob[hdr].g = (int16_t) desc_off; + blob[hdr].b = (int16_t) asc_off; + blob[hdr].a = hband_split; } for (unsigned int b = 0; b < num_vbands; b++) { - blob[header_len + num_hbands + b].r = (int16_t) vband_curves[b].size (); - blob[header_len + num_hbands + b].g = (int16_t) index_offset; - blob[header_len + num_hbands + b].b = 0; - blob[header_len + num_hbands + b].a = 0; + unsigned int hdr = header_len + num_hbands + b; + unsigned int desc_off = index_offset; for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { - unsigned int curve_off = curve_texel_offset[vband_curves[b][ci]]; - blob[index_offset].r = (int16_t) curve_off; + blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves[b][ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; index_offset++; } + + unsigned int asc_off = index_offset; + + for (unsigned int ci = 0; ci < vband_curves_asc[b].size (); ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves_asc[b][ci]]; + blob[index_offset].g = 0; + blob[index_offset].b = 0; + blob[index_offset].a = 0; + index_offset++; + } + + blob[hdr].r = (int16_t) vband_curves[b].size (); + blob[hdr].g = (int16_t) desc_off; + blob[hdr].b = (int16_t) asc_off; + blob[hdr].a = vband_split; } *output_len = total_len; diff --git a/src/glyphy-slug.glsl b/src/glyphy-slug.glsl index 3f01a21..44c31e5 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-slug.glsl @@ -111,7 +111,10 @@ float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) ivec4 hbandData = texelFetch (u_atlas, bandBase + bandIndex.y); int hCurveCount = hbandData.r; - int hDataOffset = hbandData.g; + /* Symmetric: choose rightward (desc) or leftward (asc) sort */ + float hSplit = float (hbandData.a) * GLYPHY_INV_UNITS; + bool hLeftRay = (renderCoord.x < hSplit); + int hDataOffset = hLeftRay ? hbandData.b : hbandData.g; for (int ci = 0; ci < hCurveCount; ci++) { @@ -123,23 +126,29 @@ float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; - if (max (max (p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) - break; + if (hLeftRay) { + if (min (min (p12.x, p12.z), p3.x) * pixelsPerEm.x > 0.5) break; + } else { + if (max (max (p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break; + } uint code = glyphy_calc_root_code (p12.y, p12.w, p3.y); if (code != 0U) { vec2 r = glyphy_solve_horiz_poly (p12, p3) * pixelsPerEm.x; + /* For leftward ray: saturate(0.5 - r) counts coverage from the left */ + vec2 cov = hLeftRay ? clamp (vec2 (0.5) - r, 0.0, 1.0) + : clamp (r + vec2 (0.5), 0.0, 1.0); if ((code & 1U) != 0U) { - xcov += clamp (r.x + 0.5, 0.0, 1.0); + xcov += cov.x; xwgt = max (xwgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); } if (code > 1U) { - xcov -= clamp (r.y + 0.5, 0.0, 1.0); + xcov -= cov.y; xwgt = max (xwgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); } } @@ -150,7 +159,9 @@ float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) ivec4 vbandData = texelFetch (u_atlas, bandBase + numHBands + bandIndex.x); int vCurveCount = vbandData.r; - int vDataOffset = vbandData.g; + float vSplit = float (vbandData.a) * GLYPHY_INV_UNITS; + bool vLeftRay = (renderCoord.y < vSplit); + int vDataOffset = vLeftRay ? vbandData.b : vbandData.g; for (int ci = 0; ci < vCurveCount; ci++) { @@ -162,23 +173,28 @@ float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) vec4 p12 = vec4 (raw12) * GLYPHY_INV_UNITS - vec4 (renderCoord, renderCoord); vec2 p3 = vec2 (raw3.rg) * GLYPHY_INV_UNITS - renderCoord; - if (max (max (p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) - break; + if (vLeftRay) { + if (min (min (p12.y, p12.w), p3.y) * pixelsPerEm.y > 0.5) break; + } else { + if (max (max (p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break; + } uint code = glyphy_calc_root_code (p12.x, p12.z, p3.x); if (code != 0U) { vec2 r = glyphy_solve_vert_poly (p12, p3) * pixelsPerEm.y; + vec2 cov = vLeftRay ? clamp (vec2 (0.5) - r, 0.0, 1.0) + : clamp (r + vec2 (0.5), 0.0, 1.0); if ((code & 1U) != 0U) { - ycov -= clamp (r.x + 0.5, 0.0, 1.0); + ycov -= cov.x; ywgt = max (ywgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); } if (code > 1U) { - ycov += clamp (r.y + 0.5, 0.0, 1.0); + ycov += cov.y; ywgt = max (ywgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); } } From 8eb14a35d3a9c63daf4ea0389cca0365039ff65e Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:38:45 -0600 Subject: [PATCH 22/68] Cleanup: remove win32 build, unused MIN_FONT_SIZE, fix CI apt Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/linux-build.yml | 2 +- demo/demo-common.h | 1 - win32/config.h | 86 ------------------------ win32/glyphy-demo.vcxproj | 106 ------------------------------ win32/glyphy.sln | 28 -------- win32/glyphy.vcxproj | 92 -------------------------- win32/stringize.js | 20 ------ 7 files changed, 1 insertion(+), 334 deletions(-) delete mode 100644 win32/config.h delete mode 100644 win32/glyphy-demo.vcxproj delete mode 100644 win32/glyphy.sln delete mode 100644 win32/glyphy.vcxproj delete mode 100644 win32/stringize.js diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index b520bd9..a1061d5 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install build dependencies - run: sudo apt install -y gcc g++ libfreetype6-dev libharfbuzz-dev meson freeglut3-dev libglew-dev + run: sudo apt update && sudo apt install -y gcc g++ libfreetype6-dev libharfbuzz-dev meson freeglut3-dev libglew-dev - name: Meson setup run: meson setup build - name: Meson compile diff --git a/demo/demo-common.h b/demo/demo-common.h index 38b7295..408fe84 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -124,7 +124,6 @@ #define ARRAY_LEN(Array) (sizeof (Array) / sizeof (*Array)) -#define MIN_FONT_SIZE 14 #define gl(name) \ diff --git a/win32/config.h b/win32/config.h deleted file mode 100644 index 66472a2..0000000 --- a/win32/config.h +++ /dev/null @@ -1,86 +0,0 @@ -/* config.h. Pre-canned for VS. */ - -/* Default font file. */ -#define DEFAULT_FONT "Calibri" - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_DLFCN_H */ - -/* Have freetype2 library */ -#define HAVE_FREETYPE2 - -/* Have gl library */ -#define HAVE_GL 1 - -/* Have glew library */ -#define HAVE_GLEW 1 - -/* Have glut library */ -#define HAVE_GLUT 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_STRINGS_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_UNISTD_H */ - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR ".libs/" - -/* Define to 1 if your C compiler doesn't accept -c and -o together. */ -/* #undef NO_MINUS_C_MINUS_O */ - -/* Name of package */ -#define PACKAGE "glyphy" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "http://code.google.com/p/glyphy/issues/list" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "glyphy" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "glyphy 0.2.0" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "glyphy" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "http://code.google.com/p/glyphy/" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "0.2.0" - -/* Define to the directory containing package data. */ -#define PKGDATADIR "foo" - -/* Define to 1 if you have the ANSI C header files. */ -/* #undef STDC_HEADERS */ - -/* Version number of package */ -#define VERSION "0.2.0" - -/* On Android */ -/* #undef __ANDROID__ */ diff --git a/win32/glyphy-demo.vcxproj b/win32/glyphy-demo.vcxproj deleted file mode 100644 index b5e8008..0000000 --- a/win32/glyphy-demo.vcxproj +++ /dev/null @@ -1,106 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35} - Win32Proj - glyphywindowsdemo - - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;NOMINMAX;_USE_MATH_DEFINES;%(PreprocessorDefinitions) - ..\win32;..\src;..\demo;%(AdditionalIncludeDirectories) - - - Console - true - %(AdditionalLibraryDirectories) - %(AdditionalDependencies) - - - cscript "$(MSBuildProjectDirectory)\stringize.js" "$(MSBuildProjectDirectory)\..\demo" - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - - - Console - true - true - true - - - cscript "$(MSBuildProjectDirectory)\stringize.js" "$(MSBuildProjectDirectory)\..\demo" - - - - - - - - - - - - - - - - {16458488-1c9d-4ef0-8311-a5e80ecd25d0} - - - - - - \ No newline at end of file diff --git a/win32/glyphy.sln b/win32/glyphy.sln deleted file mode 100644 index 344693b..0000000 --- a/win32/glyphy.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glyphy-demo", "glyphy-demo.vcxproj", "{0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glyphy", "glyphy.vcxproj", "{16458488-1C9D-4EF0-8311-A5E80ECD25D0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35}.Debug|Win32.ActiveCfg = Debug|Win32 - {0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35}.Debug|Win32.Build.0 = Debug|Win32 - {0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35}.Release|Win32.ActiveCfg = Release|Win32 - {0C6D51DA-FFD8-4835-A6DC-8FB6FAE65D35}.Release|Win32.Build.0 = Release|Win32 - {16458488-1C9D-4EF0-8311-A5E80ECD25D0}.Debug|Win32.ActiveCfg = Debug|Win32 - {16458488-1C9D-4EF0-8311-A5E80ECD25D0}.Debug|Win32.Build.0 = Debug|Win32 - {16458488-1C9D-4EF0-8311-A5E80ECD25D0}.Release|Win32.ActiveCfg = Release|Win32 - {16458488-1C9D-4EF0-8311-A5E80ECD25D0}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/win32/glyphy.vcxproj b/win32/glyphy.vcxproj deleted file mode 100644 index 3a2e515..0000000 --- a/win32/glyphy.vcxproj +++ /dev/null @@ -1,92 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - - - - - - - - - - {16458488-1C9D-4EF0-8311-A5E80ECD25D0} - Win32Proj - glyphy - - - - StaticLibrary - true - v140 - Unicode - - - StaticLibrary - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;HAVE_CONFIG_H;_USE_MATH_DEFINES;%(PreprocessorDefinitions) - ..\win32;..\src;%(AdditionalIncludeDirectories) - - - Windows - true - - - cscript "$(MSBuildProjectDirectory)\stringize.js" "$(MSBuildProjectDirectory)\..\src" - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - - - Windows - true - true - true - - - cscript "$(MSBuildProjectDirectory)\stringize.js" "$(MSBuildProjectDirectory)\..\src" - - - - - - \ No newline at end of file diff --git a/win32/stringize.js b/win32/stringize.js deleted file mode 100644 index de18eea..0000000 --- a/win32/stringize.js +++ /dev/null @@ -1,20 +0,0 @@ -var fs = new ActiveXObject("Scripting.FileSystemObject"); - -var enumerator = new Enumerator(fs.GetFolder(WScript.Arguments.Item(0)).Files); -for (; !enumerator.atEnd(); enumerator.moveNext()) { - var filePath = enumerator.item() + ''; - // skip if it is not a glsl file - if (!/\.glsl$/.exec(filePath)) { continue; } - var file = fs.OpenTextFile(filePath); - var text = file.ReadAll().split('\n'); - file.Close(); - - var result = fs.CreateTextFile(filePath.replace(/\.glsl$/, '-glsl.h'), true); - result.WriteLine('static const char* ' + filePath.replace(/.*\\/, '').replace(/[-\.]/g, '_') + ' = '); - for (var i in text) { - var line = (text[i] + ''); - result.WriteLine('"' + line.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '\\n"'); - } - result.WriteLine(';'); - result.Close(); -} \ No newline at end of file From 70aef1b663bb821758ffc6cf56bf8afb58d136de Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 00:42:02 -0600 Subject: [PATCH 23/68] Per-band optimal split value for symmetric optimization Instead of using the glyph midpoint for all bands, compute the optimal split per band by finding the x/y value that minimizes max(left_count, right_count). This balances the work between leftward and rightward rays more evenly, especially for asymmetric glyphs. ~2% speedup (1980 -> 2020 fps). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-encode.cc | 51 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 637a2bf..59f12d8 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -296,11 +296,32 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, * All offsets are relative to blob start. */ unsigned int index_offset = header_len + band_headers_len; - /* Split value: midpoint of glyph extent */ - int16_t hband_split = quantize ((extents->min_x + extents->max_x) * 0.5); - int16_t vband_split = quantize ((extents->min_y + extents->max_y) * 0.5); - for (unsigned int b = 0; b < num_hbands; b++) { + /* Per-band split: find x that minimizes max(left_count, right_count). + * Curves with max_x >= split are processed by rightward ray. + * Curves with min_x <= split are processed by leftward ray. + * Descending sort is by max_x, so try split at each max_x boundary. */ + int16_t hband_split; + { + auto &bc = hband_curves[b]; + unsigned int n = bc.size (); + unsigned int best_worst = n; + double best_split = (extents->min_x + extents->max_x) * 0.5; + for (unsigned int ci = 0; ci < n; ci++) { + double split = curve_max_x (&curves[bc[ci]]); + unsigned int right_count = ci + 1; /* curves with max_x >= split */ + unsigned int left_count = 0; + for (unsigned int cj = 0; cj < n; cj++) + if (curve_min_x (&curves[bc[cj]]) <= split) + left_count++; + unsigned int worst = std::max (right_count, left_count); + if (worst < best_worst) { + best_worst = worst; + best_split = split; + } + } + hband_split = quantize (best_split); + } unsigned int hdr = header_len + b; unsigned int desc_off = index_offset; @@ -329,6 +350,28 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } for (unsigned int b = 0; b < num_vbands; b++) { + int16_t vband_split; + { + auto &bc = vband_curves[b]; + unsigned int n = bc.size (); + unsigned int best_worst = n; + double best_split = (extents->min_y + extents->max_y) * 0.5; + for (unsigned int ci = 0; ci < n; ci++) { + double split = curve_max_y (&curves[bc[ci]]); + unsigned int right_count = ci + 1; + unsigned int left_count = 0; + for (unsigned int cj = 0; cj < n; cj++) + if (curve_min_y (&curves[bc[cj]]) <= split) + left_count++; + unsigned int worst = std::max (right_count, left_count); + if (worst < best_worst) { + best_worst = worst; + best_split = split; + } + } + vband_split = quantize (best_split); + } + unsigned int hdr = header_len + num_hbands + b; unsigned int desc_off = index_offset; From 66602450d8a6dc1482c6d539301593c33270a2da Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:04:53 -0600 Subject: [PATCH 24/68] Add dynamic dilation in vertex shader Dilate glyph quads by exactly half a pixel on screen, regardless of zoom level. The vertex shader computes the dilation in clip space and corrects the texcoord using the inverse 2x2 MVP Jacobian. Per-vertex data adds normal direction and inverse Jacobian for the object-to-em-space mapping. Viewport size passed as uniform. Based on SlugDilate from the Slug reference vertex shader, simplified for z=0 geometry. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-buffer.cc | 14 ++++++++++++++ demo/demo-glstate.cc | 5 +++++ demo/demo-shader.cc | 6 ++++++ demo/demo-shader.h | 7 +++++++ demo/demo-vshader.glsl | 23 +++++++++++++++++++++-- 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index a6267d6..b3daee0 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -182,6 +182,18 @@ demo_buffer_draw (demo_buffer_t *buffer) glVertexAttribPointer (loc_tex, 2, GL_FLOAT, GL_FALSE, stride, (const void *) offsetof (glyph_vertex_t, tx)); + /* a_normal: vec2 */ + GLint loc_norm = glGetAttribLocation (program, "a_normal"); + glEnableVertexAttribArray (loc_norm); + glVertexAttribPointer (loc_norm, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, nx)); + + /* a_jacobian: vec2 */ + GLint loc_jac = glGetAttribLocation (program, "a_jacobian"); + glEnableVertexAttribArray (loc_jac); + glVertexAttribPointer (loc_jac, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, jx)); + /* a_glyphLoc: uint */ GLint loc_glyph = glGetAttribLocation (program, "a_glyphLoc"); glEnableVertexAttribArray (loc_glyph); @@ -192,5 +204,7 @@ demo_buffer_draw (demo_buffer_t *buffer) glDisableVertexAttribArray (loc_pos); glDisableVertexAttribArray (loc_tex); + glDisableVertexAttribArray (loc_norm); + glDisableVertexAttribArray (loc_jac); glDisableVertexAttribArray (loc_glyph); } diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index cba03dc..805fabe 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -75,5 +75,10 @@ void demo_glstate_set_matrix (demo_glstate_t *st, float mat[16]) { glUniformMatrix4fv (glGetUniformLocation (st->program, "u_matViewProjection"), 1, GL_FALSE, mat); + + GLint viewport[4]; + glGetIntegerv (GL_VIEWPORT, viewport); + glUniform2f (glGetUniformLocation (st->program, "u_viewport"), + (float) viewport[2], (float) viewport[3]); } diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 6eab524..8bfc4f7 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -45,6 +45,12 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, v[ci].y = (float) (p.y - scale * ey); v[ci].tx = (float) ex; v[ci].ty = (float) ey; + /* Normal points outward from glyph center, length = 1 in object space */ + v[ci].nx = cx ? 1.f : -1.f; + v[ci].ny = cy ? -1.f : 1.f; /* y is flipped: screen y = -object y */ + /* Inverse Jacobian: d(texcoord) = d(position) * jac */ + v[ci].jx = (float) (1.0 / scale); + v[ci].jy = (float) (-1.0 / scale); v[ci].atlas_offset = gi->atlas_offset; } diff --git a/demo/demo-shader.h b/demo/demo-shader.h index eb7d040..075b12d 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -22,8 +22,15 @@ struct glyph_vertex_t { /* Em-space texture coordinates */ GLfloat tx; GLfloat ty; + /* Object-space normal for dilation (corner direction * scale) */ + GLfloat nx; + GLfloat ny; + /* Inverse Jacobian: maps object-space to em-space (constant across glyph) */ + GLfloat jx; + GLfloat jy; /* Atlas offset (constant across glyph) */ GLuint atlas_offset; + GLuint _padding; }; void diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index 581a953..2b5264c 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -1,9 +1,12 @@ #version 330 uniform mat4 u_matViewProjection; +uniform vec2 u_viewport; in vec2 a_position; in vec2 a_texcoord; +in vec2 a_normal; +in vec2 a_jacobian; in uint a_glyphLoc; out vec2 v_texcoord; @@ -11,7 +14,23 @@ flat out uint v_glyphLoc; void main () { - gl_Position = u_matViewProjection * vec4 (a_position, 0.0, 1.0); - v_texcoord = a_texcoord; + /* Transform undilated position to clip space */ + vec4 clipPos = u_matViewProjection * vec4 (a_position, 0.0, 1.0); + + /* Half pixel in NDC */ + vec2 halfPixelNDC = clipPos.w / u_viewport; + + /* Dilate in clip space along corner direction */ + clipPos.xy += a_normal * halfPixelNDC; + gl_Position = clipPos; + + /* Compute object-space dilation to get texcoord correction. + * d(clip.xy) = MVP * d(pos), so d(pos) = inverse(MVP_2x2) * d(clip.xy). + * For the texcoord: d(tex) = d(pos) * jacobian. */ + vec2 dClip = a_normal * halfPixelNDC; + mat2 mvp2 = mat2 (u_matViewProjection[0].xy, u_matViewProjection[1].xy); + vec2 dPos = inverse (mvp2) * dClip; + + v_texcoord = a_texcoord + dPos * a_jacobian; v_glyphLoc = a_glyphLoc; } From 563964ea3cbf4298800d871a51ee64660e1a113a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:21:19 -0600 Subject: [PATCH 25/68] Move dilation to library vertex shader, rename shader API Add glyphy-vertex.glsl with glyphy_dilate() that computes exact half-pixel dilation using the MVP matrix and viewport size. Client vertex shader just calls one function. Rename shader files and API: glyphy-slug.glsl -> glyphy-fragment.glsl glyphy_slug_shader_source -> glyphy_fragment_shader_source glyphy_slug_render -> glyphy_render New: glyphy_vertex_shader_source, glyphy_dilate Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-buffer.cc | 26 +++++------ demo/demo-fshader.glsl | 2 +- demo/demo-shader.cc | 16 +++---- demo/demo-shader.h | 12 ++--- demo/demo-vshader.glsl | 28 ++++-------- ...{glyphy-slug.glsl => glyphy-fragment.glsl} | 2 +- src/glyphy-shaders.cc | 19 +++----- src/glyphy-vertex.glsl | 45 +++++++++++++++++++ src/glyphy.h | 10 ++++- src/meson.build | 3 +- 10 files changed, 98 insertions(+), 65 deletions(-) rename src/{glyphy-slug.glsl => glyphy-fragment.glsl} (98%) create mode 100644 src/glyphy-vertex.glsl diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index b3daee0..1a98064 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -182,17 +182,17 @@ demo_buffer_draw (demo_buffer_t *buffer) glVertexAttribPointer (loc_tex, 2, GL_FLOAT, GL_FALSE, stride, (const void *) offsetof (glyph_vertex_t, tx)); - /* a_normal: vec2 */ - GLint loc_norm = glGetAttribLocation (program, "a_normal"); - glEnableVertexAttribArray (loc_norm); - glVertexAttribPointer (loc_norm, 2, GL_FLOAT, GL_FALSE, stride, - (const void *) offsetof (glyph_vertex_t, nx)); - - /* a_jacobian: vec2 */ - GLint loc_jac = glGetAttribLocation (program, "a_jacobian"); - glEnableVertexAttribArray (loc_jac); - glVertexAttribPointer (loc_jac, 2, GL_FLOAT, GL_FALSE, stride, - (const void *) offsetof (glyph_vertex_t, jx)); + /* a_corner: vec2 */ + GLint loc_corner = glGetAttribLocation (program, "a_corner"); + glEnableVertexAttribArray (loc_corner); + glVertexAttribPointer (loc_corner, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, cx)); + + /* a_texPerPos: vec2 */ + GLint loc_tpp = glGetAttribLocation (program, "a_texPerPos"); + glEnableVertexAttribArray (loc_tpp); + glVertexAttribPointer (loc_tpp, 2, GL_FLOAT, GL_FALSE, stride, + (const void *) offsetof (glyph_vertex_t, tpx)); /* a_glyphLoc: uint */ GLint loc_glyph = glGetAttribLocation (program, "a_glyphLoc"); @@ -204,7 +204,7 @@ demo_buffer_draw (demo_buffer_t *buffer) glDisableVertexAttribArray (loc_pos); glDisableVertexAttribArray (loc_tex); - glDisableVertexAttribArray (loc_norm); - glDisableVertexAttribArray (loc_jac); + glDisableVertexAttribArray (loc_corner); + glDisableVertexAttribArray (loc_tpp); glDisableVertexAttribArray (loc_glyph); } diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index fc2e7b7..233bc34 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -5,7 +5,7 @@ out vec4 fragColor; void main () { - float coverage = glyphy_slug_render (v_texcoord, v_glyphLoc); + float coverage = glyphy_render (v_texcoord, v_glyphLoc); fragColor = vec4 (0.0, 0.0, 0.0, coverage); } diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 8bfc4f7..dc18faf 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -45,12 +45,10 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, v[ci].y = (float) (p.y - scale * ey); v[ci].tx = (float) ex; v[ci].ty = (float) ey; - /* Normal points outward from glyph center, length = 1 in object space */ - v[ci].nx = cx ? 1.f : -1.f; - v[ci].ny = cy ? -1.f : 1.f; /* y is flipped: screen y = -object y */ - /* Inverse Jacobian: d(texcoord) = d(position) * jac */ - v[ci].jx = (float) (1.0 / scale); - v[ci].jy = (float) (-1.0 / scale); + v[ci].cx = cx ? 1.f : -1.f; + v[ci].cy = cy ? -1.f : 1.f; /* y flipped: screen y = -object y */ + v[ci].tpx = (float) (1.0 / scale); + v[ci].tpy = (float) (-1.0 / scale); v[ci].atlas_offset = gi->atlas_offset; } @@ -152,10 +150,12 @@ demo_shader_create_program (void) TRACE(); GLuint vshader, fshader, program; - const GLchar *vshader_sources[] = {demo_vshader_glsl}; + const GLchar *vshader_sources[] = {"#version 330\n", + glyphy_vertex_shader_source (), + demo_vshader_glsl}; vshader = compile_shader (GL_VERTEX_SHADER, ARRAY_LEN (vshader_sources), vshader_sources); const GLchar *fshader_sources[] = {"#version 330\n", - glyphy_slug_shader_source (), + glyphy_fragment_shader_source (), demo_fshader_glsl}; fshader = compile_shader (GL_FRAGMENT_SHADER, ARRAY_LEN (fshader_sources), fshader_sources); diff --git a/demo/demo-shader.h b/demo/demo-shader.h index 075b12d..a6ee994 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -22,12 +22,12 @@ struct glyph_vertex_t { /* Em-space texture coordinates */ GLfloat tx; GLfloat ty; - /* Object-space normal for dilation (corner direction * scale) */ - GLfloat nx; - GLfloat ny; - /* Inverse Jacobian: maps object-space to em-space (constant across glyph) */ - GLfloat jx; - GLfloat jy; + /* Corner direction for dilation (-1 or +1 per axis) */ + GLfloat cx; + GLfloat cy; + /* Texcoord-per-position ratio (constant across glyph) */ + GLfloat tpx; + GLfloat tpy; /* Atlas offset (constant across glyph) */ GLuint atlas_offset; GLuint _padding; diff --git a/demo/demo-vshader.glsl b/demo/demo-vshader.glsl index 2b5264c..01d16f8 100644 --- a/demo/demo-vshader.glsl +++ b/demo/demo-vshader.glsl @@ -1,12 +1,10 @@ -#version 330 - uniform mat4 u_matViewProjection; uniform vec2 u_viewport; in vec2 a_position; in vec2 a_texcoord; -in vec2 a_normal; -in vec2 a_jacobian; +in vec2 a_corner; +in vec2 a_texPerPos; in uint a_glyphLoc; out vec2 v_texcoord; @@ -14,23 +12,13 @@ flat out uint v_glyphLoc; void main () { - /* Transform undilated position to clip space */ - vec4 clipPos = u_matViewProjection * vec4 (a_position, 0.0, 1.0); - - /* Half pixel in NDC */ - vec2 halfPixelNDC = clipPos.w / u_viewport; - - /* Dilate in clip space along corner direction */ - clipPos.xy += a_normal * halfPixelNDC; - gl_Position = clipPos; + vec2 pos = a_position; + vec2 tex = a_texcoord; - /* Compute object-space dilation to get texcoord correction. - * d(clip.xy) = MVP * d(pos), so d(pos) = inverse(MVP_2x2) * d(clip.xy). - * For the texcoord: d(tex) = d(pos) * jacobian. */ - vec2 dClip = a_normal * halfPixelNDC; - mat2 mvp2 = mat2 (u_matViewProjection[0].xy, u_matViewProjection[1].xy); - vec2 dPos = inverse (mvp2) * dClip; + glyphy_dilate (pos, tex, a_corner, a_texPerPos, + u_matViewProjection, u_viewport); - v_texcoord = a_texcoord + dPos * a_jacobian; + gl_Position = u_matViewProjection * vec4 (pos, 0.0, 1.0); + v_texcoord = tex; v_glyphLoc = a_glyphLoc; } diff --git a/src/glyphy-slug.glsl b/src/glyphy-fragment.glsl similarity index 98% rename from src/glyphy-slug.glsl rename to src/glyphy-fragment.glsl index 44c31e5..cfcef4a 100644 --- a/src/glyphy-slug.glsl +++ b/src/glyphy-fragment.glsl @@ -80,7 +80,7 @@ float glyphy_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) return clamp (coverage, 0.0, 1.0); } -float glyphy_slug_render (vec2 renderCoord, uint glyphLoc_) +float glyphy_render (vec2 renderCoord, uint glyphLoc_) { vec2 emsPerPixel = fwidth (renderCoord); vec2 pixelsPerEm = 1.0 / emsPerPixel; diff --git a/src/glyphy-shaders.cc b/src/glyphy-shaders.cc index 253c06a..4c8d1c7 100644 --- a/src/glyphy-shaders.cc +++ b/src/glyphy-shaders.cc @@ -6,12 +6,6 @@ * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #ifdef HAVE_CONFIG_H @@ -20,14 +14,13 @@ #include "glyphy.h" -/* - * Shader source code - */ - #define SHADER_PATH(File) PKGDATADIR "/" File -#include "glyphy-slug-glsl.h" +#include "glyphy-fragment-glsl.h" +#include "glyphy-vertex-glsl.h" -const char * glyphy_slug_shader_source (void) { return glyphy_slug_glsl; } +const char * glyphy_fragment_shader_source (void) { return glyphy_fragment_glsl; } +const char * glyphy_vertex_shader_source (void) { return glyphy_vertex_glsl; } -const char * glyphy_slug_shader_source_path (void) { return SHADER_PATH ("glyphy-slug.glsl"); } +const char * glyphy_fragment_shader_source_path (void) { return SHADER_PATH ("glyphy-fragment.glsl"); } +const char * glyphy_vertex_shader_source_path (void) { return SHADER_PATH ("glyphy-vertex.glsl"); } diff --git a/src/glyphy-vertex.glsl b/src/glyphy-vertex.glsl new file mode 100644 index 0000000..04c8ebb --- /dev/null +++ b/src/glyphy-vertex.glsl @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. + * Based on the Slug algorithm by Eric Lengyel (MIT license). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +/* Requires GLSL 3.30 */ + + +/* Dilate a glyph quad vertex by half a pixel on screen. + * + * position: object-space vertex position (modified in place) + * texcoord: em-space sample coordinates (modified in place) + * corner: corner direction, each component -1 or +1 + * texPerPos: ratio of texcoord units to position units (e.g. upem / font_size) + * mvp: model-view-projection matrix + * viewport: viewport size in pixels + */ +void glyphy_dilate (inout vec2 position, inout vec2 texcoord, + vec2 corner, vec2 texPerPos, + mat4 mvp, vec2 viewport) +{ + vec4 clipPos = mvp * vec4 (position, 0.0, 1.0); + + /* Half pixel in clip space */ + vec2 halfPixelClip = clipPos.w / viewport; + + /* Dilate clip-space position along corner direction */ + vec2 dClip = corner * halfPixelClip; + + /* Map clip-space dilation back to object space. + * For z=0 geometry: clip.xy = pos.x * mvp[0].xy + pos.y * mvp[1].xy + mvp[3].xy + * So the 2x2 Jacobian of clip.xy w.r.t. pos.xy is (mvp[0].xy, mvp[1].xy). */ + mat2 mvp2 = mat2 (mvp[0].xy, mvp[1].xy); + vec2 dPos = inverse (mvp2) * dClip; + + position += dPos; + texcoord += dPos * texPerPos; +} diff --git a/src/glyphy.h b/src/glyphy.h index 075eb77..d87a48b 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -185,10 +185,16 @@ typedef struct { */ GLYPHY_API const char * -glyphy_slug_shader_source (void); +glyphy_fragment_shader_source (void); GLYPHY_API const char * -glyphy_slug_shader_source_path (void); +glyphy_fragment_shader_source_path (void); + +GLYPHY_API const char * +glyphy_vertex_shader_source (void); + +GLYPHY_API const char * +glyphy_vertex_shader_source_path (void); GLYPHY_API glyphy_bool_t diff --git a/src/meson.build b/src/meson.build index 54282a7..8c9ffe5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,7 +12,8 @@ glyphy_headers = [ ] glyphy_shaders = [ - [ 'glyphy-slug.glsl', 'glyphy-slug-glsl.h', 'glyphy_slug_glsl' ], + [ 'glyphy-fragment.glsl', 'glyphy-fragment-glsl.h', 'glyphy_fragment_glsl' ], + [ 'glyphy-vertex.glsl', 'glyphy-vertex-glsl.h', 'glyphy_vertex_glsl' ], ] stringize = find_program('stringize') From c99c8edfdeddb6e1885a93e5cf705f0bdde557ca Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:25:10 -0600 Subject: [PATCH 26/68] Fix Slug attribution, remove shader _path APIs Drop "(MIT license)" from Slug attribution -- the algorithm is referenced, not the code. Remove glyphy_*_shader_source_path() functions and PKGDATADIR/SHADER_PATH since shaders are embedded. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/glyphy-fragment.glsl | 2 +- src/glyphy-shaders.cc | 5 ----- src/glyphy-vertex.glsl | 2 +- src/glyphy.h | 6 ------ 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/glyphy-fragment.glsl b/src/glyphy-fragment.glsl index cfcef4a..ed544d2 100644 --- a/src/glyphy-fragment.glsl +++ b/src/glyphy-fragment.glsl @@ -1,6 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. - * Based on the Slug algorithm by Eric Lengyel (MIT license). + * Based on the Slug algorithm by Eric Lengyel. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/glyphy-shaders.cc b/src/glyphy-shaders.cc index 4c8d1c7..8c4c728 100644 --- a/src/glyphy-shaders.cc +++ b/src/glyphy-shaders.cc @@ -14,13 +14,8 @@ #include "glyphy.h" -#define SHADER_PATH(File) PKGDATADIR "/" File - #include "glyphy-fragment-glsl.h" #include "glyphy-vertex-glsl.h" const char * glyphy_fragment_shader_source (void) { return glyphy_fragment_glsl; } const char * glyphy_vertex_shader_source (void) { return glyphy_vertex_glsl; } - -const char * glyphy_fragment_shader_source_path (void) { return SHADER_PATH ("glyphy-fragment.glsl"); } -const char * glyphy_vertex_shader_source_path (void) { return SHADER_PATH ("glyphy-vertex.glsl"); } diff --git a/src/glyphy-vertex.glsl b/src/glyphy-vertex.glsl index 04c8ebb..ee44168 100644 --- a/src/glyphy-vertex.glsl +++ b/src/glyphy-vertex.glsl @@ -1,6 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. - * Based on the Slug algorithm by Eric Lengyel (MIT license). + * Based on the Slug algorithm by Eric Lengyel. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/glyphy.h b/src/glyphy.h index d87a48b..739a615 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -187,15 +187,9 @@ typedef struct { GLYPHY_API const char * glyphy_fragment_shader_source (void); -GLYPHY_API const char * -glyphy_fragment_shader_source_path (void); - GLYPHY_API const char * glyphy_vertex_shader_source (void); -GLYPHY_API const char * -glyphy_vertex_shader_source_path (void); - GLYPHY_API glyphy_bool_t glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, From 9b136b329f87533d0d0e918b97d4ce45c7bc5382 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:28:45 -0600 Subject: [PATCH 27/68] Update README for Slug-based renderer, remove empty NEWS Co-Authored-By: Claude Opus 4.6 (1M context) --- NEWS | 1 - README.md | 55 ++++++++++++++++++++++++++----------------------------- 2 files changed, 26 insertions(+), 30 deletions(-) delete mode 100644 NEWS diff --git a/NEWS b/NEWS deleted file mode 100644 index 243fa3e..0000000 --- a/NEWS +++ /dev/null @@ -1 +0,0 @@ -Initial import. diff --git a/README.md b/README.md index 3029e95..ae69deb 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,37 @@ -GLyphy is a signed-distance-field (SDF) text renderer using OpenGL ES2 shading language. +GLyphy is a GPU text renderer that renders glyph outlines directly, +using the [Slug algorithm](https://jcgt.org/published/0006/02/02/) +by Eric Lengyel for robust winding number calculation. -The main difference between GLyphy and other SDF-based OpenGL renderers is that most other projects sample the SDF into a texture. This has all the usual problems that sampling has. Ie. it distorts the outline and is low quality. +GLyphy works with quadratic Bezier curves (TrueType outlines) and +produces pixel-perfect rendering at any scale with proper antialiasing. -GLyphy instead represents the SDF using actual vectors submitted to the GPU. This results in very high quality rendering, though at a much higher runtime cost. +## Building -See this video for insight to how GLyphy works: +Requires: meson, OpenGL 3.3+, FreeType, HarfBuzz, GLUT, GLEW. -http://vimeo.com/behdad/glyphy +```sh +meson setup build +meson compile -C build +build/demo/glyphy-demo +``` -Dicussions happen on: +## How it works -https://groups.google.com/forum/#!forum/glyphy +Each glyph's outline is encoded into a compact blob stored in a GPU +buffer texture. The fragment shader computes a winding number by +casting horizontal and vertical rays against the quadratic Bezier +curves, using Lengyel's equivalence class algorithm for numerical +robustness. Curves are organized into bands for efficient early exit. ----------------------------------------------------------------------- +The vertex shader performs dynamic dilation to ensure edge pixels +are always shaded for antialiasing. -On GNOME3 and possibly other systems, if the vsync extension is not working (ie. pressing `v` in the demo doesn't have any effect), try running with `vblank_mode=0` env var. +## Notes -### Compilation instructions on Mac OS X +On GNOME3 and possibly other systems, if the vsync extension is not +working (pressing `v` in the demo has no effect), try running with +`vblank_mode=0`. -1. Install Xcode and command line tools (as of Xcode 4.3.x, from -   within `Preferences` -> `Downloads`). -2. Install [MacPorts](https://www.macports.org/install.php). -3. `sudo port install automake autoconf libtool pkgconfig freetype` -4. `./autogen.sh` -5. `make` +## License -### Compilation instructions on Windows - -See [appveyor.yml](https://github.com/behdad/glyphy/blob/master/appveyor.yml), basically first get [vcpkg](https://github.com/Microsoft/vcpkg) and install `glew`, `freetype` and `freeglut` on it, then open win32\glyphy.sln -with Visual Studio. - -### Compilation instructions for emscripten - -Assuming you have installed emscripten and have its tools on path, - -1. `NOCONFIGURE=1 ./autogen.sh` -2. `CPPFLAGS='-s USE_FREETYPE=1' LDFLAGS='-s USE_FREETYPE=1' emconfigure ./configure` -3. `make EXEEXT=.html GL_LIBS= GLUT_LIBS=` -4. The result will be located on `demo/.libs/glyphy-demo.html` (not `demo/glyphy-demo.html`) +GLyphy is licensed under the Apache License, Version 2.0. From 93506739e186833b3b2714b73a09fc7ff3cd61c7 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:35:06 -0600 Subject: [PATCH 28/68] Remove dead android build Uses ES 2.0 and old glyphy API. Android support will be addressed in harfbuzz-gpu. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/android/AndroidManifest.xml | 29 --------- demo/android/README | 57 ------------------ demo/android/build.xml | 92 ----------------------------- demo/android/jni/Android.mk | 50 ---------------- demo/android/jni/Application.mk | 11 ---- demo/android/project.properties | 14 ----- demo/android/res/values/strings.xml | 4 -- 7 files changed, 257 deletions(-) delete mode 100644 demo/android/AndroidManifest.xml delete mode 100644 demo/android/README delete mode 100644 demo/android/build.xml delete mode 100644 demo/android/jni/Android.mk delete mode 100644 demo/android/jni/Application.mk delete mode 100644 demo/android/project.properties delete mode 100644 demo/android/res/values/strings.xml diff --git a/demo/android/AndroidManifest.xml b/demo/android/AndroidManifest.xml deleted file mode 100644 index 62934bb..0000000 --- a/demo/android/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo/android/README b/demo/android/README deleted file mode 100644 index a8b1fbc..0000000 --- a/demo/android/README +++ /dev/null @@ -1,57 +0,0 @@ -To build GLyphy Demo app on Android, follow these steps: - -- Download and install Android SDK and NDK, for Android API version 18 or later. - -- Install a "standalone" toolchain with the NDK, and set PLATFORM_PREFIX env - var to point to it. You can do this by running, eg: - - ndk/build/tools/make-standalone-toolchain.sh \ - --platform=android-18 \ - --install-dir=/prefix - - Adjust platform and install-dir. - -- Make sure the cross-compile tools from PLATFORM_PREFIX are in the path. - Ie. that you can run arm-linux-androideabi-gcc. - -- Configure and install FreeType. Within the FreeType tarball run: - - ./configure --host=arm-linux-androideabi --prefix=$PLATFORM_PREFIX --without-png - make install - -- Configure and install freeglut-gles2. We want the pre-3.0 version of freeglut - which has native Android support. Get from SVN and run: - - cmake -D CMAKE_TOOLCHAIN_FILE=android_toolchain.cmake \ - -D CMAKE_INSTALL_PREFIX=$PLATFORM_PREFIX \ - -D CMAKE_BUILD_TYPE=Debug \ - -D FREEGLUT_GLES2=ON \ - -D FREEGLUT_BUILD_DEMOS=NO \ - .. - make install - -- Configure GLyphy: - - ./configure \ - --host=arm-linux-androideabi \ - --prefix=$PLATFORM_PREFIX \ - --enable-static \ - PKG_CONFIG_LIBDIR=$PLATFORM_PREFIX/lib/pkgconfig - -- Make and install the GLyphy library (but not the demos): - - make -C src install - -- Add a local.properties file to demo/android, with: - - sdk.dir=/path/to/sdk - - By default GLyphy Demo app builds against android-18 target. If you want - to override that, you can add a target=android-XX line to the above file. - -- Finally, make the demo app: - - make -C demo - -- If all goes well, you should now have demo/android/bin/GLyphyDemo-debug.apk - diff --git a/demo/android/build.xml b/demo/android/build.xml deleted file mode 100644 index b5142b7..0000000 --- a/demo/android/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/demo/android/jni/Android.mk b/demo/android/jni/Android.mk deleted file mode 100644 index 7f5ab98..0000000 --- a/demo/android/jni/Android.mk +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2010 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_MODULE := freetype -LOCAL_SRC_FILES := $(PLATFORM_PREFIX)/lib/libfreetype.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := glyphy -LOCAL_SRC_FILES := $(PLATFORM_PREFIX)/lib/libglyphy.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := glut -LOCAL_SRC_FILES := $(PLATFORM_PREFIX)/lib/libfreeglut-gles2.a -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := glyphy-demo -LOCAL_CPP_EXTENSION := .cc -LOCAL_SRC_FILES := \ - ../../matrix4x4.c \ - ../../trackball.c \ - ../../demo-atlas.cc \ - ../../demo-buffer.cc \ - ../../demo-font.cc \ - ../../demo-glstate.cc \ - ../../demo-shader.cc \ - ../../demo-view.cc \ - ../../glyphy-demo.cc \ - $(NULL) -LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv2 -lz -LOCAL_WHOLE_STATIC_LIBRARIES := gnustl_static freetype glyphy glut -include $(BUILD_SHARED_LIBRARY) - -$(call import-module,android/native_app_glue) diff --git a/demo/android/jni/Application.mk b/demo/android/jni/Application.mk deleted file mode 100644 index af5560e..0000000 --- a/demo/android/jni/Application.mk +++ /dev/null @@ -1,11 +0,0 @@ -PLATFORM_PREFIX := /home/behdad/.local/arm-linux-androideabi - -APP_STL := gnustl_static - -APP_CPPFLAGS := \ - -I$(PLATFORM_PREFIX)/include/ \ - -I$(PLATFORM_PREFIX)/include/freetype2 \ - -I$(PLATFORM_PREFIX)/include/glyphy \ - -DDEFAULT_FONT='"/system/fonts/Roboto-Regular.ttf"' \ - -DFREEGLUT_GLES2 \ - $(NULL) diff --git a/demo/android/project.properties b/demo/android/project.properties deleted file mode 100644 index ce39f2d..0000000 --- a/demo/android/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-18 diff --git a/demo/android/res/values/strings.xml b/demo/android/res/values/strings.xml deleted file mode 100644 index c5c7690..0000000 --- a/demo/android/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - GLyphy Demo - From de35e77a02816d7833fe493bb34cf7a1399322f1 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:39:23 -0600 Subject: [PATCH 29/68] Remove stale autotools files, update pkg-config description Co-Authored-By: Claude Opus 4.6 (1M context) --- ac_define_dir.m4 | 27 --------------------------- acinclude.m4 | 1 - appveyor.yml | 11 ----------- demo/demo-shader.cc | 2 +- glyphy.pc.in | 11 ----------- src/meson.build | 2 +- 6 files changed, 2 insertions(+), 52 deletions(-) delete mode 100644 ac_define_dir.m4 delete mode 100644 acinclude.m4 delete mode 100644 appveyor.yml delete mode 100644 glyphy.pc.in diff --git a/ac_define_dir.m4 b/ac_define_dir.m4 deleted file mode 100644 index d98be2f..0000000 --- a/ac_define_dir.m4 +++ /dev/null @@ -1,27 +0,0 @@ -dnl Taken from the autoconf archive (www.gnu.org) -dnl -dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) -dnl -dnl This macro _AC_DEFINEs VARNAME to the expansion of the DIR -dnl variable, taking care of fixing up ${prefix} and such. -dnl -dnl Note that the 3 argument form is only supported with autoconf 2.13 and -dnl later (i.e. only where _AC_DEFINE supports 3 arguments). -dnl -dnl Examples: -dnl -dnl AC_DEFINE_DIR(DATADIR, datadir) -dnl AC_DEFINE_DIR(PROG_PATH, bindir, [Location of installed binaries]) -dnl -dnl @version $Id: ac_define_dir.m4,v 1.1 2002/12/31 02:15:21 heiko Exp $ -dnl @author Guido Draheim , original by Alexandre Oliva - -AC_DEFUN([AC_DEFINE_DIR], [ - test "x$prefix" = xNONE && prefix="$ac_default_prefix" - test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' - ac_define_dir=`eval echo [$]$2` - ac_define_dir=`eval echo [$]ac_define_dir` - ifelse($3, , - AC_DEFINE_UNQUOTED($1, "$ac_define_dir"), - AC_DEFINE_UNQUOTED($1, "$ac_define_dir", $3)) -]) diff --git a/acinclude.m4 b/acinclude.m4 deleted file mode 100644 index 86f674f..0000000 --- a/acinclude.m4 +++ /dev/null @@ -1 +0,0 @@ -sinclude(ac_define_dir.m4) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 78e62c6..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,11 +0,0 @@ -install: - - set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% - - git clone https://github.com/Microsoft/vcpkg - - cd vcpkg - - powershell -exec bypass scripts\bootstrap.ps1 - - vcpkg install glew freetype freeglut - - vcpkg integrate install - - cd .. - -build_script: - - msbuild win32\glyphy.sln \ No newline at end of file diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index dc18faf..ea844b8 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -46,7 +46,7 @@ demo_shader_add_glyph_vertices (const glyphy_point_t &p, v[ci].tx = (float) ex; v[ci].ty = (float) ey; v[ci].cx = cx ? 1.f : -1.f; - v[ci].cy = cy ? -1.f : 1.f; /* y flipped: screen y = -object y */ + v[ci].cy = cy ? 1.f : -1.f; /* top dilates up, bottom dilates down */ v[ci].tpx = (float) (1.0 / scale); v[ci].tpy = (float) (-1.0 / scale); v[ci].atlas_offset = gi->atlas_offset; diff --git a/glyphy.pc.in b/glyphy.pc.in deleted file mode 100644 index 1867ecb..0000000 --- a/glyphy.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: GLyphy -Description: High-quality glyph rendering library using OpenGL ES2 shaders -URL: http://code.google.com/p/glyphy/ -Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lglyphy -Cflags: -I${includedir}/glyphy diff --git a/src/meson.build b/src/meson.build index 8c9ffe5..4ad1ae5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -52,7 +52,7 @@ libglyphy_dep = declare_dependency( meson.override_dependency('glyphy', libglyphy_dep) libglyphy_pc = pkgmod.generate(libglyphy, - description: 'High-quality glyph rendering library using OpenGL ES2 shaders', + description: 'High-quality GPU glyph rendering library', subdirs: meson.project_name()) install_headers(glyphy_headers, subdir: meson.project_name()) From dca79853c086f35c10d020adcbb8bf6790335c9c Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 01:57:21 -0600 Subject: [PATCH 30/68] Update copyright headers, remove inline Apache license text COPYING file contains the license. Individual files just have the copyright notice. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-atlas.cc | 5 ----- demo/demo-atlas.h | 5 ----- demo/demo-buffer.cc | 6 +----- demo/demo-buffer.h | 12 ------------ demo/demo-common.h | 12 ------------ demo/demo-font.cc | 5 ----- demo/demo-font.h | 5 ----- demo/demo-glstate.cc | 5 ----- demo/demo-glstate.h | 5 ----- demo/demo-shader.cc | 5 ----- demo/demo-shader.h | 5 ----- demo/demo-view.cc | 15 ++------------- demo/demo-view.h | 12 ------------ demo/glyphy-demo.cc | 12 ------------ src/glyphy-curves.cc | 11 ----------- src/glyphy-encode.cc | 11 ----------- src/glyphy-extents.cc | 15 ++------------- src/glyphy-fragment.glsl | 5 ----- src/glyphy-freetype.h | 7 ++----- src/glyphy-harfbuzz.h | 5 ----- src/glyphy-shaders.cc | 5 ----- src/glyphy-vertex.glsl | 5 ----- src/glyphy.h | 15 ++------------- 23 files changed, 9 insertions(+), 179 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index a01d763..b40f536 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-atlas.h b/demo/demo-atlas.h index fdaf546..bb2b7c5 100644 --- a/demo/demo-atlas.h +++ b/demo/demo-atlas.h @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifndef DEMO_ATLAS_H diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index 1a98064..ec3a402 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -1,11 +1,7 @@ /* + * Copyright 2012 Google, Inc. All Rights Reserved. * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-buffer.h b/demo/demo-buffer.h index 2948a7f..ce4222e 100644 --- a/demo/demo-buffer.h +++ b/demo/demo-buffer.h @@ -1,18 +1,6 @@ /* * Copyright 2012 Google, Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * * Google Author(s): Behdad Esfahbod */ diff --git a/demo/demo-common.h b/demo/demo-common.h index 408fe84..9df4795 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -1,18 +1,6 @@ /* * Copyright 2012 Google, Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * * Google Author(s): Behdad Esfahbod, Maysum Panju */ diff --git a/demo/demo-font.cc b/demo/demo-font.cc index f8f28a4..779a9ed 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-font.h b/demo/demo-font.h index c5f5135..3740402 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifndef DEMO_FONT_H diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index 805fabe..52cffff 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-glstate.h b/demo/demo-glstate.h index 8ea6db2..a4c439d 100644 --- a/demo/demo-glstate.h +++ b/demo/demo-glstate.h @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifndef DEMO_GLSTATE_H diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index ea844b8..81b4924 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-shader.h b/demo/demo-shader.h index a6ee994..a84b4e7 100644 --- a/demo/demo-shader.h +++ b/demo/demo-shader.h @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifndef DEMO_SHADERS_H diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 89fd560..60e8f5d 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -1,19 +1,8 @@ /* + * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski */ #ifdef HAVE_CONFIG_H diff --git a/demo/demo-view.h b/demo/demo-view.h index 9619361..8eb4848 100644 --- a/demo/demo-view.h +++ b/demo/demo-view.h @@ -1,18 +1,6 @@ /* * Copyright 2012 Google, Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * * Google Author(s): Behdad Esfahbod */ diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index 2ecd3e5..b3ece62 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -1,18 +1,6 @@ /* * Copyright 2012 Google, Inc. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * * Google Author(s): Behdad Esfahbod, Maysum Panju, Wojciech Baranowski */ diff --git a/src/glyphy-curves.cc b/src/glyphy-curves.cc index f2b07fa..3f1a495 100644 --- a/src/glyphy-curves.cc +++ b/src/glyphy-curves.cc @@ -1,17 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #ifdef HAVE_CONFIG_H diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 59f12d8..fc50f21 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -1,17 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #ifdef HAVE_CONFIG_H diff --git a/src/glyphy-extents.cc b/src/glyphy-extents.cc index 5157c46..e467459 100644 --- a/src/glyphy-extents.cc +++ b/src/glyphy-extents.cc @@ -1,19 +1,8 @@ /* + * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod */ #ifdef HAVE_CONFIG_H diff --git a/src/glyphy-fragment.glsl b/src/glyphy-fragment.glsl index ed544d2..b70eb50 100644 --- a/src/glyphy-fragment.glsl +++ b/src/glyphy-fragment.glsl @@ -2,11 +2,6 @@ * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * Based on the Slug algorithm by Eric Lengyel. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ diff --git a/src/glyphy-freetype.h b/src/glyphy-freetype.h index df6952f..6d5b798 100644 --- a/src/glyphy-freetype.h +++ b/src/glyphy-freetype.h @@ -1,11 +1,8 @@ /* + * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ /* Intentionally doesn't have include guards */ diff --git a/src/glyphy-harfbuzz.h b/src/glyphy-harfbuzz.h index 37d162d..817b64e 100644 --- a/src/glyphy-harfbuzz.h +++ b/src/glyphy-harfbuzz.h @@ -1,11 +1,6 @@ /* * Copyright 2022 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ /* Intentionally doesn't have include guards */ diff --git a/src/glyphy-shaders.cc b/src/glyphy-shaders.cc index 8c4c728..dd9a7b3 100644 --- a/src/glyphy-shaders.cc +++ b/src/glyphy-shaders.cc @@ -1,11 +1,6 @@ /* * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ #ifdef HAVE_CONFIG_H diff --git a/src/glyphy-vertex.glsl b/src/glyphy-vertex.glsl index ee44168..24d5a88 100644 --- a/src/glyphy-vertex.glsl +++ b/src/glyphy-vertex.glsl @@ -2,11 +2,6 @@ * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * Based on the Slug algorithm by Eric Lengyel. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 */ diff --git a/src/glyphy.h b/src/glyphy.h index 739a615..404619b 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -1,19 +1,8 @@ /* + * Copyright 2012 Google, Inc. All Rights Reserved. + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Google Author(s): Behdad Esfahbod, Maysum Panju */ #ifndef GLYPHY_H From b219cc6ce9ec3e74c467406e80cd1652cb09dce7 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 02:41:12 -0600 Subject: [PATCH 31/68] Require OpenGL 3.3 and guard blob int16 metadata Reject oversized glyph blobs whose signed 16-bit offsets or extents would overflow, and fail fast when the demo is run on a context older than the GLSL 330/TBO pipeline now requires. --- demo/glyphy-demo.cc | 4 ++-- src/glyphy-encode.cc | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index b3ece62..4d02213 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -226,8 +226,8 @@ main (int argc, char** argv) /* Setup glew */ if (GLEW_OK != glewInit ()) die ("Failed to initialize GL; something really broken"); - if (!glewIsSupported ("GL_VERSION_2_0")) - die ("OpenGL 2.0 not supported"); + if (!glewIsSupported ("GL_VERSION_3_3")) + die ("OpenGL 3.3 not supported"); st = demo_glstate_create (); vu = demo_view_create (st); diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index fc50f21..1c84fc0 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -18,7 +19,7 @@ /* * Encode quadratic Bezier curves into a blob for GPU rendering. * - * Blob layout (single RGBA16UI texture region): + * Blob layout (single RGBA16I texture region): * * [H-band headers (num_hbands texels)] * [V-band headers (num_vbands texels)] @@ -27,9 +28,9 @@ * * Band header texel: * R = curve count - * G = offset to curve index list (from blob start) - * B = reserved (split value for symmetric bands, future) - * A = reserved + * G = offset to descending curve index list (from blob start) + * B = offset to ascending curve index list (from blob start) + * A = split value for symmetric optimization * * Curve index texel: * R = offset to curve data (from blob start) @@ -52,6 +53,14 @@ quantize (double v) return (int16_t) round (v * GLYPHY_UNITS_PER_EM_UNIT); } +static bool +quantize_fits_i16 (double v) +{ + double q = round (v * GLYPHY_UNITS_PER_EM_UNIT); + return q >= std::numeric_limits::min () && + q <= std::numeric_limits::max (); +} + static double curve_min_x (const glyphy_curve_t *c) { @@ -228,6 +237,16 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, if (total_len > blob_size) return false; + /* Offsets and counts are stored in signed 16-bit lanes in the atlas. */ + if (total_len - 1 > (unsigned int) std::numeric_limits::max ()) + return false; + + if (!quantize_fits_i16 (extents->min_x) || + !quantize_fits_i16 (extents->min_y) || + !quantize_fits_i16 (extents->max_x) || + !quantize_fits_i16 (extents->max_y)) + return false; + unsigned int curve_data_offset = header_len + band_headers_len + total_curve_indices; /* Pack blob header */ From 74371748c9d8685c499284d7ced479ca52b72cfb Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 02:45:56 -0600 Subject: [PATCH 32/68] Make glyph dilation perspective-aware Compute vertex dilation from the projective NDC Jacobian so the half-pixel expansion stays stable under perspective and tilted views, and document the difference from Slug in the README. --- README.md | 5 +++++ src/glyphy-vertex.glsl | 25 ++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ae69deb..4155ac0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ robustness. Curves are organized into bands for efficient early exit. The vertex shader performs dynamic dilation to ensure edge pixels are always shaded for antialiasing. +Unlike Slug's reference vertex path, GLyphy computes dilation from +the projective Jacobian of NDC with respect to glyph-plane position +while using its existing quad vertex format, so the half-pixel +expansion remains perspective-aware. + ## Notes On GNOME3 and possibly other systems, if the vsync extension is not diff --git a/src/glyphy-vertex.glsl b/src/glyphy-vertex.glsl index 24d5a88..dc65a58 100644 --- a/src/glyphy-vertex.glsl +++ b/src/glyphy-vertex.glsl @@ -23,17 +23,20 @@ void glyphy_dilate (inout vec2 position, inout vec2 texcoord, { vec4 clipPos = mvp * vec4 (position, 0.0, 1.0); - /* Half pixel in clip space */ - vec2 halfPixelClip = clipPos.w / viewport; - - /* Dilate clip-space position along corner direction */ - vec2 dClip = corner * halfPixelClip; - - /* Map clip-space dilation back to object space. - * For z=0 geometry: clip.xy = pos.x * mvp[0].xy + pos.y * mvp[1].xy + mvp[3].xy - * So the 2x2 Jacobian of clip.xy w.r.t. pos.xy is (mvp[0].xy, mvp[1].xy). */ - mat2 mvp2 = mat2 (mvp[0].xy, mvp[1].xy); - vec2 dPos = inverse (mvp2) * dClip; + /* Dilate by half a pixel in NDC. Under perspective, ndc = clip.xy / clip.w, + * so the object-space delta must be computed from the projective Jacobian, + * not just the affine clip-space transform. */ + float invW = 1.0 / clipPos.w; + vec2 deltaNdc = corner / viewport; + + vec2 dNdcDx = (mvp[0].xy * clipPos.w - clipPos.xy * mvp[0].w) * (invW * invW); + vec2 dNdcDy = (mvp[1].xy * clipPos.w - clipPos.xy * mvp[1].w) * (invW * invW); + mat2 ndcJacobian = mat2 (dNdcDx, dNdcDy); + + float det = dNdcDx.x * dNdcDy.y - dNdcDx.y * dNdcDy.x; + vec2 dPos = abs (det) > 1.0 / 16777216.0 + ? inverse (ndcJacobian) * deltaNdc + : vec2 (0.0); position += dPos; texcoord += dPos * texPerPos; From cecd89b669b0000348db4d3316859aa62fb29e31 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 02:50:21 -0600 Subject: [PATCH 33/68] Require GLEW when building the demo Make the desktop demo depend on GLEW at configure time so the OpenGL 3.3 runtime gate matches the supported build configuration. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6c00d1c..4d553ca 100644 --- a/meson.build +++ b/meson.build @@ -61,7 +61,7 @@ if not freetype_dep.found() endif harfbuzz_dep = dependency('harfbuzz', version: '>= 4.0.0', required: true) gl_dep = dependency('gl', required: true) -glew_dep = dependency('glew', required: false) +glew_dep = dependency('glew', required: get_option('demo').enabled()) glut_dep = dependency('glut', method: 'pkg-config', required: false) if not glut_dep.found() From 42d588e00f3094477ab743b3ba0bc50c490ac0de Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 02:56:16 -0600 Subject: [PATCH 34/68] Remove dead demo compatibility code Drop unused demo-side reference wrappers, stale non-desktop GL paths, and the dead timer-based animation helper now that the demo only targets the current desktop GL 3.3 + GLEW configuration. --- demo/demo-atlas.cc | 5 +++- demo/demo-atlas.h | 3 --- demo/demo-buffer.cc | 19 +------------- demo/demo-buffer.h | 7 ----- demo/demo-common.h | 61 +++++++------------------------------------- demo/demo-font.cc | 18 +------------ demo/demo-font.h | 12 --------- demo/demo-glstate.cc | 13 +--------- demo/demo-glstate.h | 3 --- demo/demo-view.cc | 27 +++----------------- demo/demo-view.h | 3 --- 11 files changed, 19 insertions(+), 152 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index b40f536..7484b18 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -20,6 +20,9 @@ struct demo_atlas_t { GLuint cursor; }; +static void +demo_atlas_bind_texture (demo_atlas_t *at); + demo_atlas_t * demo_atlas_create (unsigned int capacity) @@ -62,7 +65,7 @@ demo_atlas_destroy (demo_atlas_t *at) free (at); } -void +static void demo_atlas_bind_texture (demo_atlas_t *at) { glActiveTexture (at->tex_unit); diff --git a/demo/demo-atlas.h b/demo/demo-atlas.h index bb2b7c5..de70dcd 100644 --- a/demo/demo-atlas.h +++ b/demo/demo-atlas.h @@ -27,9 +27,6 @@ demo_atlas_alloc (demo_atlas_t *at, glyphy_texel_t *data, unsigned int len); -void -demo_atlas_bind_texture (demo_atlas_t *at); - void demo_atlas_set_uniforms (demo_atlas_t *at); diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index ec3a402..4e990c3 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -11,8 +11,6 @@ #include "demo-buffer.h" struct demo_buffer_t { - unsigned int refcount; - glyphy_point_t cursor; std::vector *vertices; glyphy_extents_t ink_extents; @@ -25,7 +23,6 @@ demo_buffer_t * demo_buffer_create (void) { demo_buffer_t *buffer = (demo_buffer_t *) calloc (1, sizeof (demo_buffer_t)); - buffer->refcount = 1; buffer->vertices = new std::vector; glGenBuffers (1, &buffer->buf_name); @@ -35,17 +32,10 @@ demo_buffer_create (void) return buffer; } -demo_buffer_t * -demo_buffer_reference (demo_buffer_t *buffer) -{ - if (buffer) buffer->refcount++; - return buffer; -} - void demo_buffer_destroy (demo_buffer_t *buffer) { - if (!buffer || --buffer->refcount) + if (!buffer) return; glDeleteBuffers (1, &buffer->buf_name); @@ -81,13 +71,6 @@ demo_buffer_move_to (demo_buffer_t *buffer, buffer->cursor = *p; } -void -demo_buffer_current_point (demo_buffer_t *buffer, - glyphy_point_t *p) -{ - *p = buffer->cursor; -} - void demo_buffer_add_text (demo_buffer_t *buffer, const char *utf8, diff --git a/demo/demo-buffer.h b/demo/demo-buffer.h index ce4222e..bab9b64 100644 --- a/demo/demo-buffer.h +++ b/demo/demo-buffer.h @@ -16,9 +16,6 @@ typedef struct demo_buffer_t demo_buffer_t; demo_buffer_t * demo_buffer_create (void); -demo_buffer_t * -demo_buffer_reference (demo_buffer_t *buffer); - void demo_buffer_destroy (demo_buffer_t *buffer); @@ -35,10 +32,6 @@ void demo_buffer_move_to (demo_buffer_t *buffer, const glyphy_point_t *p); -void -demo_buffer_current_point (demo_buffer_t *buffer, - glyphy_point_t *p); - void demo_buffer_add_text (demo_buffer_t *buffer, const char *utf8, diff --git a/demo/demo-common.h b/demo/demo-common.h index 9df4795..80bc519 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -20,20 +20,6 @@ #include #include -/* Tailor config for various platforms. */ - -#ifdef EMSCRIPTEN -/* https://github.com/kripken/emscripten/issues/340 */ -# undef HAVE_GLEW - /* WebGL shaders are ES2 */ -# define GL_ES_VERSION_2_0 1 -#endif - -#if defined(__ANDROID__) -# define HAVE_GLES2 1 -# define HAVE_GLUT 1 -#endif - #ifdef _WIN32 # define HAVE_GL 1 # define HAVE_GLEW 1 @@ -41,41 +27,20 @@ # define HAVE_FREETYPE2 1 #endif -/* Get Glew out of the way. */ -#ifdef HAVE_GLEW -# include -#else -# define GLEW_OK 0 - static inline int glewInit (void) { return GLEW_OK; } - static inline int glewIsSupported (const char *s) - { return 0 == strcmp ("GL_VERSION_2_0", s); } -#endif /* HAVE_GLEW */ +#include #ifdef __APPLE__ # define GL_SILENCE_DEPRECATION #endif -/* WTF this block?! */ -#if defined(HAVE_GLES2) -# include -#elif defined(HAVE_GL) -# ifndef HAVE_GLEW -# define GL_GLEXT_PROTOTYPES 1 -# if defined(__APPLE__) -# include -# else -# include -# endif -# endif +#if defined(HAVE_GL) # if defined(__APPLE__) # include # else -# ifdef HAVE_GLEW -# ifdef _WIN32 -# include -# else -# include -# endif +# ifdef _WIN32 +# include +# else +# include # endif # endif #endif /* HAVE_GL */ @@ -92,17 +57,9 @@ -/* Logging. */ -#ifdef __ANDROID__ -# include -# define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "glyphy-demo", __VA_ARGS__)) -# define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "glyphy-demo", __VA_ARGS__)) -# define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "glyphy-demo", __VA_ARGS__)) -#else /* !__ANDROID__ */ -# define LOGI(...) ((void) fprintf (stdout, __VA_ARGS__)) -# define LOGW(...) ((void) fprintf (stderr, __VA_ARGS__)) -# define LOGE(...) ((void) fprintf (stderr, __VA_ARGS__), abort ()) -#endif +#define LOGI(...) ((void) fprintf (stdout, __VA_ARGS__)) +#define LOGW(...) ((void) fprintf (stderr, __VA_ARGS__)) +#define LOGE(...) ((void) fprintf (stderr, __VA_ARGS__), abort ()) diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 779a9ed..f785778 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -17,8 +17,6 @@ typedef std::map glyph_cache_t; struct demo_font_t { - unsigned int refcount; - hb_face_t *face; hb_font_t *font; glyph_cache_t *glyph_cache; @@ -35,7 +33,6 @@ demo_font_create (hb_face_t *face, demo_atlas_t *atlas) { demo_font_t *font = (demo_font_t *) calloc (1, sizeof (demo_font_t)); - font->refcount = 1; font->face = hb_face_reference (face); font->font = hb_font_create (face); @@ -46,17 +43,10 @@ demo_font_create (hb_face_t *face, return font; } -demo_font_t * -demo_font_reference (demo_font_t *font) -{ - if (font) font->refcount++; - return font; -} - void demo_font_destroy (demo_font_t *font) { - if (!font || --font->refcount) + if (!font) return; glyphy_curve_accumulator_destroy (font->acc); @@ -80,12 +70,6 @@ demo_font_get_font (demo_font_t *font) return font->font; } -demo_atlas_t * -demo_font_get_atlas (demo_font_t *font) -{ - return font->atlas; -} - static glyphy_bool_t accumulate_curve (const glyphy_curve_t *curve, diff --git a/demo/demo-font.h b/demo/demo-font.h index 3740402..5528936 100644 --- a/demo/demo-font.h +++ b/demo/demo-font.h @@ -11,12 +11,6 @@ #include -#ifdef _WIN32 -#define DEFAULT_FONT "Calibri" -#undef near -#undef far -#endif - typedef struct { glyphy_extents_t extents; double advance; @@ -32,9 +26,6 @@ demo_font_t * demo_font_create (hb_face_t *face, demo_atlas_t *atlas); -demo_font_t * -demo_font_reference (demo_font_t *font); - void demo_font_destroy (demo_font_t *font); @@ -45,9 +36,6 @@ demo_font_get_face (demo_font_t *font); hb_font_t * demo_font_get_font (demo_font_t *font); -demo_atlas_t * -demo_font_get_atlas (demo_font_t *font); - void demo_font_lookup_glyph (demo_font_t *font, diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index 52cffff..486fc21 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -10,8 +10,6 @@ #include "demo-glstate.h" struct demo_glstate_t { - unsigned int refcount; - GLuint program; demo_atlas_t *atlas; }; @@ -22,7 +20,6 @@ demo_glstate_create (void) TRACE(); demo_glstate_t *st = (demo_glstate_t *) calloc (1, sizeof (demo_glstate_t)); - st->refcount = 1; st->program = demo_shader_create_program (); st->atlas = demo_atlas_create (1024 * 1024); @@ -30,17 +27,10 @@ demo_glstate_create (void) return st; } -demo_glstate_t * -demo_glstate_reference (demo_glstate_t *st) -{ - if (st) st->refcount++; - return st; -} - void demo_glstate_destroy (demo_glstate_t *st) { - if (!st || --st->refcount) + if (!st) return; demo_atlas_destroy (st->atlas); @@ -76,4 +66,3 @@ demo_glstate_set_matrix (demo_glstate_t *st, float mat[16]) glUniform2f (glGetUniformLocation (st->program, "u_viewport"), (float) viewport[2], (float) viewport[3]); } - diff --git a/demo/demo-glstate.h b/demo/demo-glstate.h index a4c439d..6abdca7 100644 --- a/demo/demo-glstate.h +++ b/demo/demo-glstate.h @@ -17,9 +17,6 @@ typedef struct demo_glstate_t demo_glstate_t; demo_glstate_t * demo_glstate_create (void); -demo_glstate_t * -demo_glstate_reference (demo_glstate_t *st); - void demo_glstate_destroy (demo_glstate_t *st); diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 60e8f5d..fb03722 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -21,8 +21,6 @@ extern "C" { #endif struct demo_view_t { - unsigned int refcount; - demo_glstate_t *st; /* Output */ @@ -70,7 +68,6 @@ demo_view_create (demo_glstate_t *st) TRACE(); demo_view_t *vu = (demo_view_t *) calloc (1, sizeof (demo_view_t)); - vu->refcount = 1; vu->st = st; demo_view_reset (vu); @@ -81,17 +78,10 @@ demo_view_create (demo_glstate_t *st) return vu; } -demo_view_t * -demo_view_reference (demo_view_t *vu) -{ - if (vu) vu->refcount++; - return vu; -} - void demo_view_destroy (demo_view_t *vu) { - if (!vu || --vu->refcount) + if (!vu) return; assert (static_vu == vu); @@ -187,27 +177,17 @@ current_time (void) } static void -next_frame (demo_view_t *vu) +next_frame (void) { glutPostRedisplay (); } -static void -timed_step (int ms) -{ - demo_view_t *vu = static_vu; - if (vu->animate) { - glutTimerFunc (ms, timed_step, ms); - next_frame (vu); - } -} - static void idle_step (void) { demo_view_t *vu = static_vu; if (vu->animate) { - next_frame (vu); + next_frame (); } else glutIdleFunc (NULL); @@ -232,7 +212,6 @@ start_animation (demo_view_t *vu) { vu->num_frames = 0; vu->last_frame_time = vu->fps_start_time = current_time (); - //glutTimerFunc (1000/60, timed_step, 1000/60); glutIdleFunc (idle_step); if (!vu->has_fps_timer) { vu->has_fps_timer = true; diff --git a/demo/demo-view.h b/demo/demo-view.h index 8eb4848..cfee0d1 100644 --- a/demo/demo-view.h +++ b/demo/demo-view.h @@ -16,9 +16,6 @@ typedef struct demo_view_t demo_view_t; demo_view_t * demo_view_create (demo_glstate_t *st); -demo_view_t * -demo_view_reference (demo_view_t *vu); - void demo_view_destroy (demo_view_t *vu); From 1a26351537cb90df6f82a15e257cd51b1790b70c Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 02:58:33 -0600 Subject: [PATCH 35/68] Reuse demo glyph scratch buffer on the heap Move the per-glyph encoding scratch storage out of the stack frame and keep a reusable heap-backed buffer on demo_font_t instead. --- demo/demo-font.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/demo/demo-font.cc b/demo/demo-font.cc index f785778..4a6d265 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -22,6 +22,7 @@ struct demo_font_t { glyph_cache_t *glyph_cache; demo_atlas_t *atlas; glyphy_curve_accumulator_t *acc; + std::vector *scratch_buffer; unsigned int num_glyphs; unsigned int sum_curves; @@ -39,6 +40,7 @@ demo_font_create (hb_face_t *face, font->glyph_cache = new glyph_cache_t (); font->atlas = demo_atlas_reference (atlas); font->acc = glyphy_curve_accumulator_create (); + font->scratch_buffer = new std::vector (16384); return font; } @@ -51,6 +53,7 @@ demo_font_destroy (demo_font_t *font) glyphy_curve_accumulator_destroy (font->acc); demo_atlas_destroy (font->atlas); + delete font->scratch_buffer; delete font->glyph_cache; hb_font_destroy (font->font); hb_face_destroy (font->face); @@ -117,12 +120,11 @@ _demo_font_upload_glyph (demo_font_t *font, unsigned int glyph_index, glyph_info_t *glyph_info) { - glyphy_texel_t buffer[16384]; unsigned int output_len; encode_glyph (font, glyph_index, - buffer, ARRAY_LEN (buffer), + font->scratch_buffer->data (), font->scratch_buffer->size (), &output_len, &glyph_info->extents, &glyph_info->advance); @@ -130,7 +132,9 @@ _demo_font_upload_glyph (demo_font_t *font, glyph_info->upem = hb_face_get_upem (font->face); glyph_info->is_empty = glyphy_extents_is_empty (&glyph_info->extents); if (!glyph_info->is_empty) - glyph_info->atlas_offset = demo_atlas_alloc (font->atlas, buffer, output_len); + glyph_info->atlas_offset = demo_atlas_alloc (font->atlas, + font->scratch_buffer->data (), + output_len); } void From d9f9a33fc7a4d9b70154f323654e41c37fbb9a30 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:30:13 -0600 Subject: [PATCH 36/68] Print demo controls and drop startup traces --- demo/demo-atlas.cc | 2 -- demo/demo-common.h | 21 --------------------- demo/demo-glstate.cc | 2 -- demo/demo-shader.cc | 6 ------ demo/demo-view.cc | 26 +++++++++++++++++++++++--- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index 7484b18..250d9b2 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -27,8 +27,6 @@ demo_atlas_bind_texture (demo_atlas_t *at); demo_atlas_t * demo_atlas_create (unsigned int capacity) { - TRACE(); - demo_atlas_t *at = (demo_atlas_t *) calloc (1, sizeof (demo_atlas_t)); at->refcount = 1; diff --git a/demo/demo-common.h b/demo/demo-common.h index 80bc519..a9c59f0 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -93,25 +93,4 @@ T clamp (T v, T m, T M) return v < m ? m : v > M ? M : v; } - -#if defined(_MSC_VER) -#define DEMO_FUNC __FUNCSIG__ -#else -#define DEMO_FUNC __func__ -#endif - -struct auto_trace_t -{ - auto_trace_t (const char *func_) : func (func_) - { printf ("Enter: %s\n", func); } - - ~auto_trace_t (void) - { printf ("Leave: %s\n", func); } - - private: - const char * const func; -}; - -#define TRACE() auto_trace_t trace(DEMO_FUNC) - #endif /* DEMO_COMMON_H */ diff --git a/demo/demo-glstate.cc b/demo/demo-glstate.cc index 486fc21..b6fc29c 100644 --- a/demo/demo-glstate.cc +++ b/demo/demo-glstate.cc @@ -17,8 +17,6 @@ struct demo_glstate_t { demo_glstate_t * demo_glstate_create (void) { - TRACE(); - demo_glstate_t *st = (demo_glstate_t *) calloc (1, sizeof (demo_glstate_t)); st->program = demo_shader_create_program (); diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 81b4924..9fa40c5 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -71,8 +71,6 @@ compile_shader (GLenum type, GLsizei count, const GLchar** sources) { - TRACE(); - GLuint shader; GLint compiled; @@ -107,8 +105,6 @@ static GLuint link_program (GLuint vshader, GLuint fshader) { - TRACE(); - GLuint program; GLint linked; @@ -142,8 +138,6 @@ link_program (GLuint vshader, GLuint demo_shader_create_program (void) { - TRACE(); - GLuint vshader, fshader, program; const GLchar *vshader_sources[] = {"#version 330\n", glyphy_vertex_shader_source (), diff --git a/demo/demo-view.cc b/demo/demo-view.cc index fb03722..7b6667a 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -65,8 +65,6 @@ demo_view_t *static_vu; demo_view_t * demo_view_create (demo_glstate_t *st) { - TRACE(); - demo_view_t *vu = (demo_view_t *) calloc (1, sizeof (demo_view_t)); vu->st = st; @@ -508,7 +506,29 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) void demo_view_print_help (demo_view_t *vu) { - LOGI ("Welcome to GLyphy demo\n"); + (void) vu; + + LOGI ("GLyphy demo controls\n"); + LOGI ("Keyboard:\n"); + LOGI (" Esc, q Quit\n"); + LOGI (" Space Toggle animation\n"); + LOGI (" f Toggle fullscreen\n"); + LOGI (" v Toggle vsync\n"); + LOGI (" =, - Zoom in or out\n"); + LOGI (" [, ] Stretch or shrink horizontally\n"); + LOGI (" {, } Stretch or shrink vertically\n"); + LOGI (" h j k l Pan\n"); + LOGI (" Arrow keys Pan\n"); + LOGI (" r Reset view\n"); + LOGI ("Mouse:\n"); + LOGI (" Left drag Pan\n"); + LOGI (" Middle drag Zoom\n"); + LOGI (" Wheel Zoom\n"); + LOGI (" Right drag Rotate\n"); + LOGI (" Shift + right drag Adjust perspective\n"); + LOGI (" Right drag and release Animate rotation\n"); + LOGI (" Right click Toggle animation\n"); + LOGI ("\n"); } From 1be18539b19e9049e256c8c8e8ca6fb0421d4ce8 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:35:44 -0600 Subject: [PATCH 37/68] Add demo help shortcuts --- demo/demo-view.cc | 3 +++ demo/glyphy-demo.cc | 2 ++ 2 files changed, 5 insertions(+) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 7b6667a..968481c 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -305,6 +305,9 @@ demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y) case ' ': demo_view_toggle_animation (vu); break; + case '?': + demo_view_print_help (vu); + break; case 'v': demo_view_toggle_vsync (vu); break; diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index 4d02213..a49d12d 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -167,6 +167,8 @@ show_usage(const char *path) " -f fontfile the font file (e.g. /Library/Fonts/Microsoft/Verdana.ttf)\n" "\n", name, name); + demo_view_print_help (NULL); + free(p); } From d4a1aaa90cf5c21c2244e3fd44dc03e62324d82a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:35:57 -0600 Subject: [PATCH 38/68] Add demo sRGB toggle shortcut --- demo/demo-view.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 968481c..a163b9d 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -308,6 +308,9 @@ demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y) case '?': demo_view_print_help (vu); break; + case 's': + demo_view_toggle_srgb (vu); + break; case 'v': demo_view_toggle_vsync (vu); break; @@ -516,6 +519,7 @@ demo_view_print_help (demo_view_t *vu) LOGI (" Esc, q Quit\n"); LOGI (" Space Toggle animation\n"); LOGI (" f Toggle fullscreen\n"); + LOGI (" s Toggle sRGB framebuffer\n"); LOGI (" v Toggle vsync\n"); LOGI (" =, - Zoom in or out\n"); LOGI (" [, ] Stretch or shrink horizontally\n"); From e80c8ee4f15925191035745a2461f8294bb34bee Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:36:05 -0600 Subject: [PATCH 39/68] Log demo animation state changes --- demo/demo-view.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index a163b9d..f716b4d 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -221,6 +221,7 @@ static void demo_view_toggle_animation (demo_view_t *vu) { vu->animate = !vu->animate; + LOGI ("Setting animation %s.\n", vu->animate ? "on" : "off"); if (vu->animate) start_animation (vu); } From 299369023bfda6a6daabb24c236577047ad85ccb Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:44:43 -0600 Subject: [PATCH 40/68] meson: link OpenGL framework for macOS demo --- demo/meson.build | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/meson.build b/demo/meson.build index d2c8474..1e9ccf2 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -46,9 +46,15 @@ demo_shader_sources += custom_target('default font', capture: true, command: [ hexify, 'static const char default_font[]', '@INPUT@' ]) +demo_deps = [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glut_dep] + +if host_machine.system() == 'darwin' + demo_deps += dependency('appleframeworks', modules: ['OpenGL']) +endif + glyphy_demo = executable('glyphy-demo', demo_sources + demo_shader_sources, cpp_args: [], include_directories: [confinc, srcinc], - dependencies: [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glut_dep], + dependencies: demo_deps, link_with: [libglyphy], install: true) From a93b6f1807172af3e5ae8c0798e120070689809a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:58:46 -0600 Subject: [PATCH 41/68] Revert "meson: link OpenGL framework for macOS demo" This reverts commit 299369023bfda6a6daabb24c236577047ad85ccb. --- demo/meson.build | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/demo/meson.build b/demo/meson.build index 1e9ccf2..d2c8474 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -46,15 +46,9 @@ demo_shader_sources += custom_target('default font', capture: true, command: [ hexify, 'static const char default_font[]', '@INPUT@' ]) -demo_deps = [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glut_dep] - -if host_machine.system() == 'darwin' - demo_deps += dependency('appleframeworks', modules: ['OpenGL']) -endif - glyphy_demo = executable('glyphy-demo', demo_sources + demo_shader_sources, cpp_args: [], include_directories: [confinc, srcinc], - dependencies: demo_deps, + dependencies: [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glut_dep], link_with: [libglyphy], install: true) From b6e01e5c9e2d703cbc8c7536c49902fedfba960a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 10:51:57 -0600 Subject: [PATCH 42/68] Request sRGB window and clean up demo toggles --- demo/demo-common.h | 5 +++++ demo/demo-view.cc | 31 +++++++++++++++++++++---------- demo/glyphy-demo.cc | 6 +++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/demo/demo-common.h b/demo/demo-common.h index a9c59f0..7b6d8d5 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -51,6 +51,11 @@ # include # else # include +# ifdef __has_include +# if __has_include() +# include +# endif +# endif # endif #endif diff --git a/demo/demo-view.cc b/demo/demo-view.cc index f716b4d..60084bb 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -230,34 +230,45 @@ demo_view_toggle_animation (demo_view_t *vu) static void demo_view_toggle_vsync (demo_view_t *vu) { - vu->vsync = !vu->vsync; - LOGI ("Setting vsync %s.\n", vu->vsync ? "on" : "off"); + GLint vsync = !vu->vsync; #if defined(__APPLE__) - CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &vu->vsync); + CGLSetParameter (CGLGetCurrentContext (), kCGLCPSwapInterval, &vsync); +#elif defined(FREEGLUT) + glutSwapInterval (vsync); #elif defined(__WGLEW__) if (wglewIsSupported ("WGL_EXT_swap_control")) - wglSwapIntervalEXT (vu->vsync); + wglSwapIntervalEXT (vsync); else - LOGW ("WGL_EXT_swal_control not supported; failed to set vsync\n"); + { + LOGW ("WGL_EXT_swap_control not supported; failed to set vsync\n"); + return; + } #elif defined(__GLXEW_H__) if (glxewIsSupported ("GLX_SGI_swap_control")) - glXSwapIntervalSGI (vu->vsync); + glXSwapIntervalSGI (vsync); else - LOGW ("GLX_SGI_swap_control not supported; failed to set vsync\n"); + { + LOGW ("GLX_SGI_swap_control not supported; failed to set vsync\n"); + return; + } #else - LOGW ("No vsync extension found; failed to set vsync\n"); + LOGW ("No vsync extension found; failed to set vsync\n"); + return; #endif + vu->vsync = vsync; + LOGI ("Setting vsync %s.\n", vu->vsync ? "on" : "off"); } static void demo_view_toggle_srgb (demo_view_t *vu) { - vu->srgb = !vu->srgb; - LOGI ("Setting sRGB framebuffer %s.\n", vu->srgb ? "on" : "off"); + glyphy_bool_t srgb = !vu->srgb; #if defined(GL_FRAMEBUFFER_SRGB) && defined(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) GLboolean available = false; if ((glewIsSupported ("GL_ARB_framebuffer_sRGB") || glewIsSupported ("GL_EXT_framebuffer_sRGB")) && (glGetBooleanv (GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &available), available)) { + vu->srgb = srgb; + LOGI ("Setting sRGB framebuffer %s.\n", vu->srgb ? "on" : "off"); if (vu->srgb) glEnable (GL_FRAMEBUFFER_SRGB); else diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index a49d12d..d2bc562 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -216,7 +216,11 @@ main (int argc, char** argv) /* Setup glut */ glutInit (&argc, argv); glutInitWindowSize (WINDOW_W, WINDOW_H); - glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB); + unsigned int display_mode = GLUT_DOUBLE | GLUT_RGB; +#ifdef GLUT_SRGB + display_mode |= GLUT_SRGB; +#endif + glutInitDisplayMode (display_mode); int window = glutCreateWindow ("GLyphy Demo"); glutReshapeFunc (reshape_func); glutDisplayFunc (display_func); From 2ecf25198238a0e4101dee94d237178f34760602 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:08:21 -0600 Subject: [PATCH 43/68] Warn about GNOME vsync overrides --- demo/glyphy-demo.cc | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index d2bc562..1407b0d 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -172,6 +172,79 @@ show_usage(const char *path) free(p); } +static bool +contains_case_insensitive (const char *haystack, const char *needle) +{ + if (!haystack || !needle) + return false; + + size_t needle_len = strlen (needle); + if (!needle_len) + return true; + + for (const char *h = haystack; *h; h++) { + size_t i = 0; + while (i < needle_len && + h[i] && + tolower ((unsigned char) h[i]) == tolower ((unsigned char) needle[i])) + i++; + if (i == needle_len) + return true; + } + + return false; +} + +static bool +running_on_gnome_desktop (void) +{ + const char *desktops[] = { + getenv ("XDG_CURRENT_DESKTOP"), + getenv ("XDG_SESSION_DESKTOP"), + getenv ("DESKTOP_SESSION"), + }; + + for (unsigned int i = 0; i < ARRAY_LEN (desktops); i++) { + if (contains_case_insensitive (desktops[i], "gnome")) + return true; + } + + return false; +} + +static bool +using_mesa_gl (void) +{ + const char *strings[] = { + (const char *) glGetString (GL_VENDOR), + (const char *) glGetString (GL_RENDERER), + (const char *) glGetString (GL_VERSION), + }; + + for (unsigned int i = 0; i < ARRAY_LEN (strings); i++) { + if (contains_case_insensitive (strings[i], "mesa")) + return true; + } + + return false; +} + +static void +warn_about_vsync_override (void) +{ + const char *vblank_mode = getenv ("vblank_mode"); + if (vblank_mode) { + LOGW ("Using vblank_mode=%s from the environment.\n", vblank_mode); + return; + } + + if (!running_on_gnome_desktop () || !using_mesa_gl ()) + return; + + LOGW ("GNOME/Mesa detected with vblank_mode unset.\n"); + LOGW ("If toggling vsync with `v` has no effect, restart with `vblank_mode=0`.\n"); +} + int main (int argc, char** argv) { @@ -238,6 +311,7 @@ main (int argc, char** argv) st = demo_glstate_create (); vu = demo_view_create (st); demo_view_print_help (vu); + warn_about_vsync_override (); hb_blob_t *blob = NULL; if (font_path) From 9dec1811d49d0b6e80560259d6d755ac2f0d3f7d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:06:08 -0600 Subject: [PATCH 44/68] meson: use Apple GL frameworks on macOS --- meson.build | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index 4d553ca..b8f93c0 100644 --- a/meson.build +++ b/meson.build @@ -60,20 +60,25 @@ if not freetype_dep.found() freetype_dep = dependency('Freetype', method: 'cmake', required: true) endif harfbuzz_dep = dependency('harfbuzz', version: '>= 4.0.0', required: true) -gl_dep = dependency('gl', required: true) glew_dep = dependency('glew', required: get_option('demo').enabled()) -glut_dep = dependency('glut', method: 'pkg-config', required: false) +if host_machine.system() == 'darwin' + gl_dep = dependency('appleframeworks', modules: ['OpenGL'], required: true) + glut_dep = dependency('appleframeworks', modules: ['GLUT'], required: get_option('demo').enabled()) +else + gl_dep = dependency('gl', required: true) + glut_dep = dependency('glut', method: 'pkg-config', required: false) -if not glut_dep.found() - glut_dep = dependency('GLUT', method: 'pkg-config', required: false) -endif + if not glut_dep.found() + glut_dep = dependency('GLUT', method: 'pkg-config', required: false) + endif -if not glut_dep.found() - glut_dep = dependency('GLUT', method: 'cmake', required: false) -endif + if not glut_dep.found() + glut_dep = dependency('GLUT', method: 'cmake', required: false) + endif -if not glut_dep.found() - glut_dep = dependency('FreeGLUT', method: 'cmake', required: get_option('demo').enabled()) + if not glut_dep.found() + glut_dep = dependency('FreeGLUT', method: 'cmake', required: get_option('demo').enabled()) + endif endif prefix = get_option('prefix') From 4dbed0a6eaa6635be3f89eed073b74f9085f8727 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:13:13 -0600 Subject: [PATCH 45/68] demo: request core profile on macOS --- demo/glyphy-demo.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index 1407b0d..07b08d1 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -292,6 +292,11 @@ main (int argc, char** argv) unsigned int display_mode = GLUT_DOUBLE | GLUT_RGB; #ifdef GLUT_SRGB display_mode |= GLUT_SRGB; +#endif +#ifdef __APPLE__ +#ifdef GLUT_3_2_CORE_PROFILE + display_mode |= GLUT_3_2_CORE_PROFILE; +#endif #endif glutInitDisplayMode (display_mode); int window = glutCreateWindow ("GLyphy Demo"); @@ -303,6 +308,7 @@ main (int argc, char** argv) glutMotionFunc (motion_func); /* Setup glew */ + glewExperimental = GL_TRUE; if (GLEW_OK != glewInit ()) die ("Failed to initialize GL; something really broken"); if (!glewIsSupported ("GL_VERSION_3_3")) From 8a25f87448c7ae6cad3b5fd512502881f9b6e3b2 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:14:15 -0600 Subject: [PATCH 46/68] demo: bind a VAO for core profile --- demo/demo-buffer.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demo/demo-buffer.cc b/demo/demo-buffer.cc index 4e990c3..83a7fd7 100644 --- a/demo/demo-buffer.cc +++ b/demo/demo-buffer.cc @@ -16,6 +16,7 @@ struct demo_buffer_t { glyphy_extents_t ink_extents; glyphy_extents_t logical_extents; bool dirty; + GLuint vao_name; GLuint buf_name; }; @@ -25,6 +26,7 @@ demo_buffer_create (void) demo_buffer_t *buffer = (demo_buffer_t *) calloc (1, sizeof (demo_buffer_t)); buffer->vertices = new std::vector; + glGenVertexArrays (1, &buffer->vao_name); glGenBuffers (1, &buffer->buf_name); demo_buffer_clear (buffer); @@ -38,6 +40,7 @@ demo_buffer_destroy (demo_buffer_t *buffer) if (!buffer) return; + glDeleteVertexArrays (1, &buffer->vao_name); glDeleteBuffers (1, &buffer->buf_name); delete buffer->vertices; free (buffer); @@ -139,6 +142,7 @@ demo_buffer_draw (demo_buffer_t *buffer) GLint program; glGetIntegerv (GL_CURRENT_PROGRAM, &program); + glBindVertexArray (buffer->vao_name); glBindBuffer (GL_ARRAY_BUFFER, buffer->buf_name); if (buffer->dirty) { glBufferData (GL_ARRAY_BUFFER, @@ -186,4 +190,5 @@ demo_buffer_draw (demo_buffer_t *buffer) glDisableVertexAttribArray (loc_corner); glDisableVertexAttribArray (loc_tpp); glDisableVertexAttribArray (loc_glyph); + glBindVertexArray (0); } From a736fd7a614570839dd58feab66ecc319a138a8b Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:15:21 -0600 Subject: [PATCH 47/68] demo: detect sRGB framebuffer in core profile --- demo/demo-view.cc | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 60084bb..cc2e4f5 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -263,10 +263,25 @@ static void demo_view_toggle_srgb (demo_view_t *vu) { glyphy_bool_t srgb = !vu->srgb; -#if defined(GL_FRAMEBUFFER_SRGB) && defined(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) - GLboolean available = false; - if ((glewIsSupported ("GL_ARB_framebuffer_sRGB") || glewIsSupported ("GL_EXT_framebuffer_sRGB")) && - (glGetBooleanv (GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &available), available)) { +#if defined(GL_FRAMEBUFFER_SRGB) + bool available = false; +#if defined(GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) && defined(GL_BACK_LEFT) + GLint encoding = GL_LINEAR; + glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER, + GL_BACK_LEFT, + GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, + &encoding); + available = encoding == GL_SRGB; +#endif +#if defined(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) + if (!available && + (glewIsSupported ("GL_ARB_framebuffer_sRGB") || glewIsSupported ("GL_EXT_framebuffer_sRGB"))) { + GLboolean ext_available = false; + glGetBooleanv (GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &ext_available); + available = ext_available; + } +#endif + if (available) { vu->srgb = srgb; LOGI ("Setting sRGB framebuffer %s.\n", vu->srgb ? "on" : "off"); if (vu->srgb) @@ -274,8 +289,10 @@ demo_view_toggle_srgb (demo_view_t *vu) else glDisable (GL_FRAMEBUFFER_SRGB); } else -#endif LOGW ("No sRGB framebuffer extension found; failed to set sRGB framebuffer\n"); +#else + LOGW ("No sRGB framebuffer extension found; failed to set sRGB framebuffer\n"); +#endif } static void From 5e23b6de9dde8ec7ad387379a8cf59843c0a75f4 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:26:53 -0600 Subject: [PATCH 48/68] demo: port from GLUT to GLFW Replace GLUT/FreeGLUT windowing with GLFW3: - Explicit main loop instead of glutMainLoop - GLFW native vsync via glfwSwapInterval (removes platform-specific CGLSetParameter/wglSwapIntervalEXT/glXSwapIntervalSGI code) - Proper core profile context hints (no GLUT_3_2_CORE_PROFILE ifdef) - Proper scroll callback instead of GLUT_WHEEL_UP/DOWN hack - Split keyboard handling into key callback (special keys) and char callback (printable characters) - Dirty-flag based redraw with glfwWaitEvents when idle Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-common.h | 24 +---- demo/demo-view.cc | 252 +++++++++++++++++++------------------------- demo/demo-view.h | 16 ++- demo/glyphy-demo.cc | 93 ++++++++++------ demo/meson.build | 2 +- meson.build | 19 +--- 6 files changed, 186 insertions(+), 220 deletions(-) diff --git a/demo/demo-common.h b/demo/demo-common.h index 7b6d8d5..d5692e7 100644 --- a/demo/demo-common.h +++ b/demo/demo-common.h @@ -23,7 +23,7 @@ #ifdef _WIN32 # define HAVE_GL 1 # define HAVE_GLEW 1 -# define HAVE_GLUT 1 +# define HAVE_GLFW 1 # define HAVE_FREETYPE2 1 #endif @@ -36,30 +36,10 @@ #if defined(HAVE_GL) # if defined(__APPLE__) # include -# else -# ifdef _WIN32 -# include -# else -# include -# endif # endif #endif /* HAVE_GL */ -/* Finally, Glut. */ -#ifdef HAVE_GLUT -# if defined(__APPLE__) -# include -# else -# include -# ifdef __has_include -# if __has_include() -# include -# endif -# endif -# endif -#endif - - +#include #define LOGI(...) ((void) fprintf (stdout, __VA_ARGS__)) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index cc2e4f5..dab9cac 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -22,6 +22,7 @@ extern "C" { struct demo_view_t { demo_glstate_t *st; + GLFWwindow *window; /* Output */ GLint vsync; @@ -52,6 +53,11 @@ struct demo_view_t { long fps_start_time; long last_frame_time; bool has_fps_timer; + double fps_timer_interval; + double fps_timer_last; + + /* Dirty flag for redraw */ + bool needs_redraw; /* Window geometry just before going fullscreen */ int x; @@ -60,19 +66,16 @@ struct demo_view_t { int height; }; -demo_view_t *static_vu; - demo_view_t * -demo_view_create (demo_glstate_t *st) +demo_view_create (demo_glstate_t *st, GLFWwindow *window) { demo_view_t *vu = (demo_view_t *) calloc (1, sizeof (demo_view_t)); vu->st = st; + vu->window = window; + vu->needs_redraw = true; demo_view_reset (vu); - assert (!static_vu); - static_vu = vu; - return vu; } @@ -82,9 +85,6 @@ demo_view_destroy (demo_view_t *vu) if (!vu) return; - assert (static_vu == vu); - static_vu = NULL; - free (vu); } @@ -99,6 +99,7 @@ demo_view_reset (demo_view_t *vu) trackball (vu->quat , 0.0, 0.0, 0.0, 0.0); vset (vu->rot_axis, 0., 0., 1.); vu->rot_speed = ANIMATION_SPEED / 1000.; + vu->needs_redraw = true; } @@ -171,38 +172,7 @@ demo_view_apply_transform (demo_view_t *vu, float *mat) static long current_time (void) { - return glutGet (GLUT_ELAPSED_TIME); -} - -static void -next_frame (void) -{ - glutPostRedisplay (); -} - -static void -idle_step (void) -{ - demo_view_t *vu = static_vu; - if (vu->animate) { - next_frame (); - } - else - glutIdleFunc (NULL); -} - -static void -print_fps (int ms) -{ - demo_view_t *vu = static_vu; - if (vu->animate) { - glutTimerFunc (ms, print_fps, ms); - long t = current_time (); - LOGI ("%gfps\n", vu->num_frames * 1000. / (t - vu->fps_start_time)); - vu->num_frames = 0; - vu->fps_start_time = t; - } else - vu->has_fps_timer = false; + return (long) (glfwGetTime () * 1000.0); } static void @@ -210,11 +180,9 @@ start_animation (demo_view_t *vu) { vu->num_frames = 0; vu->last_frame_time = vu->fps_start_time = current_time (); - glutIdleFunc (idle_step); - if (!vu->has_fps_timer) { - vu->has_fps_timer = true; - glutTimerFunc (5000, print_fps, 5000); - } + vu->fps_timer_interval = 5.0; + vu->fps_timer_last = glfwGetTime (); + vu->has_fps_timer = true; } static void @@ -231,30 +199,7 @@ static void demo_view_toggle_vsync (demo_view_t *vu) { GLint vsync = !vu->vsync; -#if defined(__APPLE__) - CGLSetParameter (CGLGetCurrentContext (), kCGLCPSwapInterval, &vsync); -#elif defined(FREEGLUT) - glutSwapInterval (vsync); -#elif defined(__WGLEW__) - if (wglewIsSupported ("WGL_EXT_swap_control")) - wglSwapIntervalEXT (vsync); - else - { - LOGW ("WGL_EXT_swap_control not supported; failed to set vsync\n"); - return; - } -#elif defined(__GLXEW_H__) - if (glxewIsSupported ("GLX_SGI_swap_control")) - glXSwapIntervalSGI (vsync); - else - { - LOGW ("GLX_SGI_swap_control not supported; failed to set vsync\n"); - return; - } -#else - LOGW ("No vsync extension found; failed to set vsync\n"); - return; -#endif + glfwSwapInterval (vsync); vu->vsync = vsync; LOGI ("Setting vsync %s.\n", vu->vsync ? "on" : "off"); } @@ -300,14 +245,13 @@ demo_view_toggle_fullscreen (demo_view_t *vu) { vu->fullscreen = !vu->fullscreen; if (vu->fullscreen) { - vu->x = glutGet (GLUT_WINDOW_X); - vu->y = glutGet (GLUT_WINDOW_Y); - vu->width = glutGet (GLUT_WINDOW_WIDTH); - vu->height = glutGet (GLUT_WINDOW_HEIGHT); - glutFullScreen (); + glfwGetWindowPos (vu->window, &vu->x, &vu->y); + glfwGetWindowSize (vu->window, &vu->width, &vu->height); + GLFWmonitor *monitor = glfwGetPrimaryMonitor (); + const GLFWvidmode *mode = glfwGetVideoMode (monitor); + glfwSetWindowMonitor (vu->window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); } else { - glutReshapeWindow (vu->width, vu->height); - glutPositionWindow (vu->x, vu->y); + glfwSetWindowMonitor (vu->window, NULL, vu->x, vu->y, vu->width, vu->height, 0); } } @@ -317,37 +261,15 @@ void demo_view_reshape_func (demo_view_t *vu, int width, int height) { glViewport (0, 0, width, height); - glutPostRedisplay (); + vu->needs_redraw = true; } #define STEP 1.05 void -demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y) +demo_view_char_func (demo_view_t *vu, unsigned int codepoint) { - switch (key) + switch (codepoint) { - case '\033': - case 'q': - exit (0); - break; - - case ' ': - demo_view_toggle_animation (vu); - break; - case '?': - demo_view_print_help (vu); - break; - case 's': - demo_view_toggle_srgb (vu); - break; - case 'v': - demo_view_toggle_vsync (vu); - break; - - case 'f': - demo_view_toggle_fullscreen (vu); - break; - case '=': demo_view_scale (vu, STEP, STEP); break; @@ -382,61 +304,90 @@ demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y) demo_view_translate (vu, -.1, 0); break; - case 'r': - demo_view_reset (vu); - break; - default: return; } - glutPostRedisplay (); + vu->needs_redraw = true; } void -demo_view_special_func (demo_view_t *vu, int key, int x, int y) +demo_view_key_func (demo_view_t *vu, int key, int scancode, int action, int mods) { + if (action != GLFW_PRESS) + return; + switch (key) { - case GLUT_KEY_UP: + case GLFW_KEY_ESCAPE: + case GLFW_KEY_Q: + glfwSetWindowShouldClose (vu->window, GLFW_TRUE); + break; + + case GLFW_KEY_SPACE: + demo_view_toggle_animation (vu); + break; + case GLFW_KEY_SLASH: + if (mods & GLFW_MOD_SHIFT) + demo_view_print_help (vu); + break; + case GLFW_KEY_S: + demo_view_toggle_srgb (vu); + break; + case GLFW_KEY_V: + demo_view_toggle_vsync (vu); + break; + + case GLFW_KEY_F: + demo_view_toggle_fullscreen (vu); + break; + + case GLFW_KEY_R: + demo_view_reset (vu); + break; + + case GLFW_KEY_UP: demo_view_translate (vu, 0, -.1); break; - case GLUT_KEY_DOWN: + case GLFW_KEY_DOWN: demo_view_translate (vu, 0, +.1); break; - case GLUT_KEY_LEFT: + case GLFW_KEY_LEFT: demo_view_translate (vu, +.1, 0); break; - case GLUT_KEY_RIGHT: + case GLFW_KEY_RIGHT: demo_view_translate (vu, -.1, 0); break; default: return; } - glutPostRedisplay (); + vu->needs_redraw = true; } void -demo_view_mouse_func (demo_view_t *vu, int button, int state, int x, int y) +demo_view_mouse_func (demo_view_t *vu, int button, int action, int mods) { - if (state == GLUT_DOWN) { + if (action == GLFW_PRESS) { vu->buttons |= (1 << button); vu->click_handled = false; } else vu->buttons &= ~(1 << button); - vu->modifiers = glutGetModifiers (); + vu->modifiers = mods; + + double x, y; + glfwGetCursorPos (vu->window, &x, &y); switch (button) { - case GLUT_RIGHT_BUTTON: - switch (state) { - case GLUT_DOWN: + case GLFW_MOUSE_BUTTON_RIGHT: + switch (action) { + case GLFW_PRESS: if (vu->animate) { demo_view_toggle_animation (vu); vu->click_handled = true; } break; - case GLUT_UP: + case GLFW_RELEASE: if (!vu->animate) { if (!vu->dragged && !vu->click_handled) @@ -451,31 +402,31 @@ demo_view_mouse_func (demo_view_t *vu, int button, int state, int x, int y) break; } break; - -#if !defined(GLUT_WHEEL_UP) -#define GLUT_WHEEL_UP 3 -#define GLUT_WHEEL_DOWN 4 -#endif - - case GLUT_WHEEL_UP: - demo_view_scale (vu, STEP, STEP); - break; - - case GLUT_WHEEL_DOWN: - demo_view_scale (vu, 1. / STEP, 1. / STEP); - break; } vu->beginx = vu->lastx = x; vu->beginy = vu->lasty = y; vu->dragged = false; - glutPostRedisplay (); + vu->needs_redraw = true; } void -demo_view_motion_func (demo_view_t *vu, int x, int y) +demo_view_scroll_func (demo_view_t *vu, double xoffset, double yoffset) { + if (yoffset > 0) + demo_view_scale (vu, STEP, STEP); + else if (yoffset < 0) + demo_view_scale (vu, 1. / STEP, 1. / STEP); + vu->needs_redraw = true; +} + +void +demo_view_motion_func (demo_view_t *vu, double x, double y) +{ + if (!vu->buttons) + return; + vu->dragged = true; int viewport[4]; @@ -483,7 +434,7 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) GLuint width = viewport[2]; GLuint height = viewport[3]; - if (vu->buttons & (1 << GLUT_LEFT_BUTTON)) + if (vu->buttons & (1 << GLFW_MOUSE_BUTTON_LEFT)) { { demo_view_translate (vu, @@ -492,9 +443,9 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) } } - if (vu->buttons & (1 << GLUT_RIGHT_BUTTON)) + if (vu->buttons & (1 << GLFW_MOUSE_BUTTON_RIGHT)) { - if (vu->modifiers & GLUT_ACTIVE_SHIFT) { + if (vu->modifiers & GLFW_MOD_SHIFT) { /* adjust perspective */ demo_view_scale_perspective (vu, 1 - ((y - vu->lasty) / height) * 5); } else { @@ -520,7 +471,7 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) } } - if (vu->buttons & (1 << GLUT_MIDDLE_BUTTON)) + if (vu->buttons & (1 << GLFW_MOUSE_BUTTON_MIDDLE)) { /* scale */ double factor = 1 - ((y - vu->lasty) / height) * 5; @@ -535,7 +486,7 @@ demo_view_motion_func (demo_view_t *vu, int x, int y) vu->lasty = y; vu->lastt = current_time (); - glutPostRedisplay (); + vu->needs_redraw = true; } void @@ -586,6 +537,18 @@ demo_view_display (demo_view_t *vu, demo_buffer_t *buffer) advance_frame (vu, new_time - vu->last_frame_time); vu->last_frame_time = new_time; + /* FPS reporting */ + if (vu->animate && vu->has_fps_timer) { + double now = glfwGetTime (); + if (now - vu->fps_timer_last >= vu->fps_timer_interval) { + long t = current_time (); + LOGI ("%gfps\n", vu->num_frames * 1000. / (t - vu->fps_start_time)); + vu->num_frames = 0; + vu->fps_start_time = t; + vu->fps_timer_last = now; + } + } + int viewport[4]; glGetIntegerv (GL_VIEWPORT, viewport); GLint width = viewport[2]; @@ -616,7 +579,8 @@ demo_view_display (demo_view_t *vu, demo_buffer_t *buffer) demo_buffer_draw (buffer); - glutSwapBuffers (); + glfwSwapBuffers (vu->window); + vu->needs_redraw = false; } void @@ -628,3 +592,9 @@ demo_view_setup (demo_view_t *vu) demo_view_toggle_srgb (vu); demo_glstate_setup (vu->st); } + +bool +demo_view_should_redraw (demo_view_t *vu) +{ + return vu->needs_redraw || vu->animate; +} diff --git a/demo/demo-view.h b/demo/demo-view.h index cfee0d1..c09791a 100644 --- a/demo/demo-view.h +++ b/demo/demo-view.h @@ -14,7 +14,7 @@ typedef struct demo_view_t demo_view_t; demo_view_t * -demo_view_create (demo_glstate_t *st); +demo_view_create (demo_glstate_t *st, GLFWwindow *window); void demo_view_destroy (demo_view_t *vu); @@ -27,16 +27,19 @@ void demo_view_reshape_func (demo_view_t *vu, int width, int height); void -demo_view_keyboard_func (demo_view_t *vu, unsigned char key, int x, int y); +demo_view_key_func (demo_view_t *vu, int key, int scancode, int action, int mods); void -demo_view_special_func (demo_view_t *view, int key, int x, int y); +demo_view_char_func (demo_view_t *vu, unsigned int codepoint); void -demo_view_mouse_func (demo_view_t *vu, int button, int state, int x, int y); +demo_view_mouse_func (demo_view_t *vu, int button, int action, int mods); void -demo_view_motion_func (demo_view_t *vu, int x, int y); +demo_view_scroll_func (demo_view_t *vu, double xoffset, double yoffset); + +void +demo_view_motion_func (demo_view_t *vu, double x, double y); void demo_view_print_help (demo_view_t *vu); @@ -47,5 +50,8 @@ demo_view_display (demo_view_t *vu, demo_buffer_t *buffer); void demo_view_setup (demo_view_t *vu); +bool +demo_view_should_redraw (demo_view_t *vu); + #endif /* DEMO_VIEW_H */ diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index 07b08d1..b12ba71 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -114,39 +114,39 @@ static int getopt(int argc, char *argv[], char *opts) #endif static void -reshape_func (int width, int height) +framebuffer_size_func (GLFWwindow *window, int width, int height) { demo_view_reshape_func (vu, width, height); } static void -keyboard_func (unsigned char key, int x, int y) +key_func (GLFWwindow *window, int key, int scancode, int action, int mods) { - demo_view_keyboard_func (vu, key, x, y); + demo_view_key_func (vu, key, scancode, action, mods); } static void -special_func (int key, int x, int y) +char_func (GLFWwindow *window, unsigned int codepoint) { - demo_view_special_func (vu, key, x, y); + demo_view_char_func (vu, codepoint); } static void -mouse_func (int button, int state, int x, int y) +mouse_func (GLFWwindow *window, int button, int action, int mods) { - demo_view_mouse_func (vu, button, state, x, y); + demo_view_mouse_func (vu, button, action, mods); } static void -motion_func (int x, int y) +scroll_func (GLFWwindow *window, double xoffset, double yoffset) { - demo_view_motion_func (vu, x, y); + demo_view_scroll_func (vu, xoffset, yoffset); } static void -display_func (void) +cursor_func (GLFWwindow *window, double x, double y) { - demo_view_display (vu, buffer); + demo_view_motion_func (vu, x, y); } static void @@ -245,11 +245,17 @@ warn_about_vsync_override (void) LOGW ("If toggling vsync with `v` has no effect, restart with `vblank_mode=0`.\n"); } +static void +glfw_error_callback (int error, const char *description) +{ + LOGW ("GLFW error %d: %s\n", error, description); +} + int main (int argc, char** argv) { /* Process received parameters */ -# include "default-text.h" +# include "default-text.h" const char *text = NULL; const char *font_path = NULL; char arg; @@ -286,28 +292,34 @@ main (int argc, char** argv) return 1; } - /* Setup glut */ - glutInit (&argc, argv); - glutInitWindowSize (WINDOW_W, WINDOW_H); - unsigned int display_mode = GLUT_DOUBLE | GLUT_RGB; -#ifdef GLUT_SRGB - display_mode |= GLUT_SRGB; -#endif + /* Setup GLFW */ + glfwSetErrorCallback (glfw_error_callback); + if (!glfwInit ()) + die ("Failed to initialize GLFW"); + + glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ -#ifdef GLUT_3_2_CORE_PROFILE - display_mode |= GLUT_3_2_CORE_PROFILE; -#endif + glfwWindowHint (GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif - glutInitDisplayMode (display_mode); - int window = glutCreateWindow ("GLyphy Demo"); - glutReshapeFunc (reshape_func); - glutDisplayFunc (display_func); - glutKeyboardFunc (keyboard_func); - glutSpecialFunc (special_func); - glutMouseFunc (mouse_func); - glutMotionFunc (motion_func); - - /* Setup glew */ + glfwWindowHint (GLFW_SRGB_CAPABLE, GLFW_TRUE); + + GLFWwindow *window = glfwCreateWindow (WINDOW_W, WINDOW_H, "GLyphy Demo", NULL, NULL); + if (!window) { + glfwTerminate (); + die ("Failed to create GLFW window"); + } + glfwMakeContextCurrent (window); + + glfwSetFramebufferSizeCallback (window, framebuffer_size_func); + glfwSetKeyCallback (window, key_func); + glfwSetCharCallback (window, char_func); + glfwSetMouseButtonCallback (window, mouse_func); + glfwSetScrollCallback (window, scroll_func); + glfwSetCursorPosCallback (window, cursor_func); + + /* Setup GLEW */ glewExperimental = GL_TRUE; if (GLEW_OK != glewInit ()) die ("Failed to initialize GL; something really broken"); @@ -315,7 +327,7 @@ main (int argc, char** argv) die ("OpenGL 3.3 not supported"); st = demo_glstate_create (); - vu = demo_view_create (st); + vu = demo_view_create (st, window); demo_view_print_help (vu); warn_about_vsync_override (); @@ -348,7 +360,17 @@ main (int argc, char** argv) demo_font_print_stats (font); demo_view_setup (vu); - glutMainLoop (); + + /* Main loop */ + while (!glfwWindowShouldClose (window)) + { + if (demo_view_should_redraw (vu)) + demo_view_display (vu, buffer); + else + glfwWaitEvents (); + + glfwPollEvents (); + } demo_buffer_destroy (buffer); demo_font_destroy (font); @@ -359,7 +381,8 @@ main (int argc, char** argv) demo_view_destroy (vu); demo_glstate_destroy (st); - glutDestroyWindow (window); + glfwDestroyWindow (window); + glfwTerminate (); return 0; } diff --git a/demo/meson.build b/demo/meson.build index d2c8474..466d9ab 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -49,6 +49,6 @@ demo_shader_sources += custom_target('default font', glyphy_demo = executable('glyphy-demo', demo_sources + demo_shader_sources, cpp_args: [], include_directories: [confinc, srcinc], - dependencies: [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glut_dep], + dependencies: [freetype_dep, harfbuzz_dep, gl_dep, glew_dep, glfw_dep], link_with: [libglyphy], install: true) diff --git a/meson.build b/meson.build index b8f93c0..a159a57 100644 --- a/meson.build +++ b/meson.build @@ -61,24 +61,11 @@ if not freetype_dep.found() endif harfbuzz_dep = dependency('harfbuzz', version: '>= 4.0.0', required: true) glew_dep = dependency('glew', required: get_option('demo').enabled()) +glfw_dep = dependency('glfw3', required: get_option('demo').enabled()) if host_machine.system() == 'darwin' gl_dep = dependency('appleframeworks', modules: ['OpenGL'], required: true) - glut_dep = dependency('appleframeworks', modules: ['GLUT'], required: get_option('demo').enabled()) else gl_dep = dependency('gl', required: true) - glut_dep = dependency('glut', method: 'pkg-config', required: false) - - if not glut_dep.found() - glut_dep = dependency('GLUT', method: 'pkg-config', required: false) - endif - - if not glut_dep.found() - glut_dep = dependency('GLUT', method: 'cmake', required: false) - endif - - if not glut_dep.found() - glut_dep = dependency('FreeGLUT', method: 'cmake', required: get_option('demo').enabled()) - endif endif prefix = get_option('prefix') @@ -99,8 +86,8 @@ endif if glew_dep.found() glyphy_conf.set('HAVE_GLEW', 1) endif -if glut_dep.found() - glyphy_conf.set('HAVE_GLUT', 1) +if glfw_dep.found() + glyphy_conf.set('HAVE_GLFW', 1) endif glyphy_conf.set_quoted('PKGDATADIR', pkgdatadir) From 2127d487e8b0d0f0f9a19fab2978e76e8f3464c2 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:28:41 -0600 Subject: [PATCH 49/68] demo: fix GLEW init with core profile glewInit() calls glGetString(GL_EXTENSIONS) which generates GL_INVALID_ENUM in a core profile context. Ignore the return value and clear the GL error; the GL_VERSION_3_3 check that follows is the real gate. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/glyphy-demo.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index b12ba71..c44cbf2 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -321,8 +321,10 @@ main (int argc, char** argv) /* Setup GLEW */ glewExperimental = GL_TRUE; - if (GLEW_OK != glewInit ()) - die ("Failed to initialize GL; something really broken"); + glewInit (); + /* glewInit() generates GL_INVALID_ENUM in core profile; clear it. */ + while (glGetError () != GL_NO_ERROR) + ; if (!glewIsSupported ("GL_VERSION_3_3")) die ("OpenGL 3.3 not supported"); From 1262b090a4a5edbe73fb3359f03135122f62c7a7 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:34:02 -0600 Subject: [PATCH 50/68] demo: fix HiDPI support with GLFW - Set initial viewport from framebuffer size (not window size) - Use window size for mouse coordinate math since GLFW cursor positions are in screen coordinates, not framebuffer pixels - Poll events before first render Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-view.cc | 8 ++++---- demo/glyphy-demo.cc | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index dab9cac..da2c3e4 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -429,10 +429,10 @@ demo_view_motion_func (demo_view_t *vu, double x, double y) vu->dragged = true; - int viewport[4]; - glGetIntegerv (GL_VIEWPORT, viewport); - GLuint width = viewport[2]; - GLuint height = viewport[3]; + /* Use window size, not framebuffer size, since GLFW cursor + * positions are in screen coordinates. */ + int width, height; + glfwGetWindowSize (vu->window, &width, &height); if (vu->buttons & (1 << GLFW_MOUSE_BUTTON_LEFT)) { diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index c44cbf2..a6a501f 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -328,6 +328,14 @@ main (int argc, char** argv) if (!glewIsSupported ("GL_VERSION_3_3")) die ("OpenGL 3.3 not supported"); + /* Set initial viewport from framebuffer size (may differ from window + * size on HiDPI displays). */ + { + int fb_width, fb_height; + glfwGetFramebufferSize (window, &fb_width, &fb_height); + glViewport (0, 0, fb_width, fb_height); + } + st = demo_glstate_create (); vu = demo_view_create (st, window); demo_view_print_help (vu); @@ -366,12 +374,12 @@ main (int argc, char** argv) /* Main loop */ while (!glfwWindowShouldClose (window)) { + glfwPollEvents (); + if (demo_view_should_redraw (vu)) demo_view_display (vu, buffer); else glfwWaitEvents (); - - glfwPollEvents (); } demo_buffer_destroy (buffer); From f495ada7098e5bb8fb9b95c98defaee4679f1a48 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:37:44 -0600 Subject: [PATCH 51/68] demo: fix first-frame blurriness on Wayland HiDPI Render an initial frame, poll events to let the Wayland compositor configure the surface at the correct content scale, then render a second frame at the right resolution. Also set viewport from framebuffer size every frame to avoid stale viewport state on HiDPI displays. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-view.cc | 8 +++----- demo/glyphy-demo.cc | 9 +++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index da2c3e4..e47cd72 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -260,7 +260,6 @@ demo_view_toggle_fullscreen (demo_view_t *vu) void demo_view_reshape_func (demo_view_t *vu, int width, int height) { - glViewport (0, 0, width, height); vu->needs_redraw = true; } @@ -549,10 +548,9 @@ demo_view_display (demo_view_t *vu, demo_buffer_t *buffer) } } - int viewport[4]; - glGetIntegerv (GL_VIEWPORT, viewport); - GLint width = viewport[2]; - GLint height = viewport[3]; + int width, height; + glfwGetFramebufferSize (vu->window, &width, &height); + glViewport (0, 0, width, height); float mat[16]; diff --git a/demo/glyphy-demo.cc b/demo/glyphy-demo.cc index a6a501f..af471ea 100644 --- a/demo/glyphy-demo.cc +++ b/demo/glyphy-demo.cc @@ -371,6 +371,15 @@ main (int argc, char** argv) demo_view_setup (vu); + /* Render initial frame, then + * process events so the Wayland compositor + * can configure the surface at the correct + * content scale, then render a + * second frame at the right resolution. */ + demo_view_display (vu, buffer); + glfwPollEvents (); + demo_view_display (vu, buffer); + /* Main loop */ while (!glfwWindowShouldClose (window)) { From ddaa6f12f3ff2469f40d3c11e450a5c408f49282 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:39:18 -0600 Subject: [PATCH 52/68] demo: fix mouse jump on first drag Track cursor position continuously from the cursor callback instead of querying glfwGetCursorPos in the mouse button handler. On Wayland, the two can disagree, causing a position jump on the first drag. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-view.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index e47cd72..43871fc 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -34,6 +34,7 @@ struct demo_view_t { int modifiers; bool dragged; bool click_handled; + double cursorx, cursory; double beginx, beginy; double lastx, lasty, lastt; double dx,dy, dt; @@ -373,8 +374,7 @@ demo_view_mouse_func (demo_view_t *vu, int button, int action, int mods) vu->buttons &= ~(1 << button); vu->modifiers = mods; - double x, y; - glfwGetCursorPos (vu->window, &x, &y); + double x = vu->cursorx, y = vu->cursory; switch (button) { @@ -423,6 +423,9 @@ demo_view_scroll_func (demo_view_t *vu, double xoffset, double yoffset) void demo_view_motion_func (demo_view_t *vu, double x, double y) { + vu->cursorx = x; + vu->cursory = y; + if (!vu->buttons) return; From 878201879d6e085b5e8dd24b62add9e12295dfbe Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:42:39 -0600 Subject: [PATCH 53/68] demo: use double-precision timing for smooth animation Integer millisecond timing caused jitter at high frame rates (vsync off) because frame deltas alternated between 0 and 1ms. Switch to double-precision seconds throughout the animation and FPS reporting code. Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/demo-view.cc | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 43871fc..3f2ed45 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -48,11 +48,11 @@ struct demo_view_t { /* Animation */ float rot_axis[3]; - float rot_speed; + double rot_speed; bool animate; int num_frames; - long fps_start_time; - long last_frame_time; + double fps_start_time; + double last_frame_time; bool has_fps_timer; double fps_timer_interval; double fps_timer_last; @@ -90,7 +90,7 @@ demo_view_destroy (demo_view_t *vu) } -#define ANIMATION_SPEED 1. /* Default speed, in radians second. */ +#define ANIMATION_SPEED 1. /* Default speed, in radians per second. */ void demo_view_reset (demo_view_t *vu) { @@ -99,7 +99,7 @@ demo_view_reset (demo_view_t *vu) vu->translate.x = vu->translate.y = 0; trackball (vu->quat , 0.0, 0.0, 0.0, 0.0); vset (vu->rot_axis, 0., 0., 1.); - vu->rot_speed = ANIMATION_SPEED / 1000.; + vu->rot_speed = ANIMATION_SPEED; vu->needs_redraw = true; } @@ -169,11 +169,11 @@ demo_view_apply_transform (demo_view_t *vu, float *mat) } -/* return current time in milli-seconds */ -static long +/* return current time in seconds */ +static double current_time (void) { - return (long) (glfwGetTime () * 1000.0); + return glfwGetTime (); } static void @@ -182,7 +182,7 @@ start_animation (demo_view_t *vu) vu->num_frames = 0; vu->last_frame_time = vu->fps_start_time = current_time (); vu->fps_timer_interval = 5.0; - vu->fps_timer_last = glfwGetTime (); + vu->fps_timer_last = current_time (); vu->has_fps_timer = true; } @@ -522,7 +522,7 @@ demo_view_print_help (demo_view_t *vu) static void -advance_frame (demo_view_t *vu, long dtime) +advance_frame (demo_view_t *vu, double dtime) { if (vu->animate) { float dquat[4]; @@ -535,18 +535,17 @@ advance_frame (demo_view_t *vu, long dtime) void demo_view_display (demo_view_t *vu, demo_buffer_t *buffer) { - long new_time = current_time (); + double new_time = current_time (); advance_frame (vu, new_time - vu->last_frame_time); vu->last_frame_time = new_time; /* FPS reporting */ if (vu->animate && vu->has_fps_timer) { - double now = glfwGetTime (); + double now = current_time (); if (now - vu->fps_timer_last >= vu->fps_timer_interval) { - long t = current_time (); - LOGI ("%gfps\n", vu->num_frames * 1000. / (t - vu->fps_start_time)); + LOGI ("%gfps\n", vu->num_frames / (now - vu->fps_start_time)); vu->num_frames = 0; - vu->fps_start_time = t; + vu->fps_start_time = now; vu->fps_timer_last = now; } } From b5437ed5863c2b707fd578d556ed5f1935ccd84d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:42:07 -0600 Subject: [PATCH 54/68] demo: use GLFW sRGB capability hint --- demo/demo-view.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index 3f2ed45..fc03aa9 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -211,13 +211,18 @@ demo_view_toggle_srgb (demo_view_t *vu) glyphy_bool_t srgb = !vu->srgb; #if defined(GL_FRAMEBUFFER_SRGB) bool available = false; +#ifdef HAVE_GLFW + available = glfwGetWindowAttrib (vu->window, GLFW_SRGB_CAPABLE) == GLFW_TRUE; +#endif #if defined(GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) && defined(GL_BACK_LEFT) - GLint encoding = GL_LINEAR; - glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER, - GL_BACK_LEFT, - GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, - &encoding); - available = encoding == GL_SRGB; + if (!available) { + GLint encoding = GL_LINEAR; + glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER, + GL_BACK_LEFT, + GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, + &encoding); + available = encoding == GL_SRGB; + } #endif #if defined(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) if (!available && From 74b934acbc3d355503e9cead1215c38598924553 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:45:24 -0600 Subject: [PATCH 55/68] demo: probe sRGB by toggling framebuffer state --- demo/demo-view.cc | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/demo/demo-view.cc b/demo/demo-view.cc index fc03aa9..9c51a19 100644 --- a/demo/demo-view.cc +++ b/demo/demo-view.cc @@ -210,37 +210,27 @@ demo_view_toggle_srgb (demo_view_t *vu) { glyphy_bool_t srgb = !vu->srgb; #if defined(GL_FRAMEBUFFER_SRGB) - bool available = false; -#ifdef HAVE_GLFW - available = glfwGetWindowAttrib (vu->window, GLFW_SRGB_CAPABLE) == GLFW_TRUE; -#endif -#if defined(GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) && defined(GL_BACK_LEFT) - if (!available) { - GLint encoding = GL_LINEAR; - glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER, - GL_BACK_LEFT, - GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, - &encoding); - available = encoding == GL_SRGB; - } -#endif -#if defined(GL_FRAMEBUFFER_SRGB_CAPABLE_EXT) - if (!available && - (glewIsSupported ("GL_ARB_framebuffer_sRGB") || glewIsSupported ("GL_EXT_framebuffer_sRGB"))) { - GLboolean ext_available = false; - glGetBooleanv (GL_FRAMEBUFFER_SRGB_CAPABLE_EXT, &ext_available); - available = ext_available; - } -#endif - if (available) { + while (glGetError () != GL_NO_ERROR) + ; + + if (srgb) + glEnable (GL_FRAMEBUFFER_SRGB); + else + glDisable (GL_FRAMEBUFFER_SRGB); + + if (glGetError () == GL_NO_ERROR) { vu->srgb = srgb; LOGI ("Setting sRGB framebuffer %s.\n", vu->srgb ? "on" : "off"); + } else { + /* Restore the previous state if the driver rejected the toggle. */ if (vu->srgb) glEnable (GL_FRAMEBUFFER_SRGB); else glDisable (GL_FRAMEBUFFER_SRGB); - } else - LOGW ("No sRGB framebuffer extension found; failed to set sRGB framebuffer\n"); + while (glGetError () != GL_NO_ERROR) + ; + LOGW ("Failed to set sRGB framebuffer state\n"); + } #else LOGW ("No sRGB framebuffer extension found; failed to set sRGB framebuffer\n"); #endif From d3983b2a19cfaf0bc3df8d171177b50a11be524f Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 11:54:41 -0600 Subject: [PATCH 56/68] Rename demo shaders to vertex and fragment --- .../{demo-fshader.glsl => demo-fragment.glsl} | 0 demo/demo-shader.cc | 42 ++++++++++--------- demo/{demo-vshader.glsl => demo-vertex.glsl} | 0 demo/meson.build | 4 +- 4 files changed, 25 insertions(+), 21 deletions(-) rename demo/{demo-fshader.glsl => demo-fragment.glsl} (100%) rename demo/{demo-vshader.glsl => demo-vertex.glsl} (100%) diff --git a/demo/demo-fshader.glsl b/demo/demo-fragment.glsl similarity index 100% rename from demo/demo-fshader.glsl rename to demo/demo-fragment.glsl diff --git a/demo/demo-shader.cc b/demo/demo-shader.cc index 9fa40c5..4142270 100644 --- a/demo/demo-shader.cc +++ b/demo/demo-shader.cc @@ -9,8 +9,8 @@ #include "demo-shader.h" -#include "demo-vshader-glsl.h" -#include "demo-fshader-glsl.h" +#include "demo-vertex-glsl.h" +#include "demo-fragment-glsl.h" void @@ -102,18 +102,18 @@ compile_shader (GLenum type, } static GLuint -link_program (GLuint vshader, - GLuint fshader) +link_program (GLuint vertex_shader, + GLuint fragment_shader) { GLuint program; GLint linked; program = glCreateProgram (); - glAttachShader (program, vshader); - glAttachShader (program, fshader); + glAttachShader (program, vertex_shader); + glAttachShader (program, fragment_shader); glLinkProgram (program); - glDeleteShader (vshader); - glDeleteShader (fshader); + glDeleteShader (vertex_shader); + glDeleteShader (fragment_shader); glGetProgramiv (program, GL_LINK_STATUS, &linked); if (!linked) { @@ -138,16 +138,20 @@ link_program (GLuint vshader, GLuint demo_shader_create_program (void) { - GLuint vshader, fshader, program; - const GLchar *vshader_sources[] = {"#version 330\n", - glyphy_vertex_shader_source (), - demo_vshader_glsl}; - vshader = compile_shader (GL_VERTEX_SHADER, ARRAY_LEN (vshader_sources), vshader_sources); - const GLchar *fshader_sources[] = {"#version 330\n", - glyphy_fragment_shader_source (), - demo_fshader_glsl}; - fshader = compile_shader (GL_FRAGMENT_SHADER, ARRAY_LEN (fshader_sources), fshader_sources); - - program = link_program (vshader, fshader); + GLuint vertex_shader, fragment_shader, program; + const GLchar *vertex_shader_sources[] = {"#version 330\n", + glyphy_vertex_shader_source (), + demo_vertex_glsl}; + vertex_shader = compile_shader (GL_VERTEX_SHADER, + ARRAY_LEN (vertex_shader_sources), + vertex_shader_sources); + const GLchar *fragment_shader_sources[] = {"#version 330\n", + glyphy_fragment_shader_source (), + demo_fragment_glsl}; + fragment_shader = compile_shader (GL_FRAGMENT_SHADER, + ARRAY_LEN (fragment_shader_sources), + fragment_shader_sources); + + program = link_program (vertex_shader, fragment_shader); return program; } diff --git a/demo/demo-vshader.glsl b/demo/demo-vertex.glsl similarity index 100% rename from demo/demo-vshader.glsl rename to demo/demo-vertex.glsl diff --git a/demo/meson.build b/demo/meson.build index 466d9ab..f27509e 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -20,8 +20,8 @@ demo_sources = [ ] demo_shaders = [ - [ 'demo-fshader.glsl', 'demo-fshader-glsl.h', 'demo_fshader_glsl' ], - [ 'demo-vshader.glsl', 'demo-vshader-glsl.h', 'demo_vshader_glsl' ], + [ 'demo-fragment.glsl', 'demo-fragment-glsl.h', 'demo_fragment_glsl' ], + [ 'demo-vertex.glsl', 'demo-vertex-glsl.h', 'demo_vertex_glsl' ], [ 'default-text.txt', 'default-text.h', 'default_text' ], ] From 1e13881d169746e8ea737ea6f022a7d8394152d5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:00:41 -0600 Subject: [PATCH 57/68] Abort on unsupported cubic outlines --- src/glyphy-freetype.h | 9 +++++---- src/glyphy-harfbuzz.h | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/glyphy-freetype.h b/src/glyphy-freetype.h index 6d5b798..9631ed5 100644 --- a/src/glyphy-freetype.h +++ b/src/glyphy-freetype.h @@ -14,6 +14,8 @@ extern "C" { #endif +#include +#include #include #include FT_FREETYPE_H #include FT_OUTLINE_H @@ -63,11 +65,10 @@ static int glyphy_freetype(cubic_to) (FT_Vector *control1, FT_Vector *control2, FT_Vector *to, glyphy_curve_accumulator_t *acc) { - /* TODO: cubics not supported yet; need cu2qu converter */ (void) control1; (void) control2; - glyphy_point_t p1 = {(double) to->x, (double) to->y}; - glyphy_curve_accumulator_line_to (acc, &p1); - return glyphy_curve_accumulator_successful (acc) ? FT_Err_Ok : FT_Err_Out_Of_Memory; + (void) to; (void) acc; + fprintf (stderr, "glyphy: cubic outlines are unsupported currently\n"); + abort (); } static FT_Error diff --git a/src/glyphy-harfbuzz.h b/src/glyphy-harfbuzz.h index 817b64e..b43f69f 100644 --- a/src/glyphy-harfbuzz.h +++ b/src/glyphy-harfbuzz.h @@ -12,6 +12,8 @@ extern "C" { #endif +#include +#include #include @@ -71,11 +73,13 @@ glyphy_harfbuzz(cubic_to) (hb_draw_funcs_t *dfuncs, float to_x, float to_y, void *user_data) { - /* TODO: cubics not supported yet; need cu2qu converter */ + (void) dfuncs; (void) acc; (void) st; (void) control1_x; (void) control1_y; (void) control2_x; (void) control2_y; - glyphy_point_t p1 = {(double) to_x, (double) to_y}; - glyphy_curve_accumulator_line_to (acc, &p1); + (void) to_x; (void) to_y; + (void) user_data; + fprintf (stderr, "glyphy: cubic outlines are unsupported currently\n"); + abort (); } static void From 29e6f4dc1824dd73ba44b47aabc080e3858c5b8d Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:21:51 -0600 Subject: [PATCH 58/68] Report atlas usage in demo stats --- demo/demo-atlas.cc | 6 ++++++ demo/demo-atlas.h | 3 +++ demo/demo-font.cc | 10 ++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/demo/demo-atlas.cc b/demo/demo-atlas.cc index 250d9b2..1e88c4c 100644 --- a/demo/demo-atlas.cc +++ b/demo/demo-atlas.cc @@ -98,3 +98,9 @@ demo_atlas_alloc (demo_atlas_t *at, return offset; } + +unsigned int +demo_atlas_get_used (demo_atlas_t *at) +{ + return at->cursor; +} diff --git a/demo/demo-atlas.h b/demo/demo-atlas.h index de70dcd..cea75d1 100644 --- a/demo/demo-atlas.h +++ b/demo/demo-atlas.h @@ -27,6 +27,9 @@ demo_atlas_alloc (demo_atlas_t *at, glyphy_texel_t *data, unsigned int len); +unsigned int +demo_atlas_get_used (demo_atlas_t *at); + void demo_atlas_set_uniforms (demo_atlas_t *at); diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 4a6d265..68ed589 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -152,8 +152,14 @@ demo_font_lookup_glyph (demo_font_t *font, void demo_font_print_stats (demo_font_t *font) { - LOGI ("%3d glyphs; avg curves%6.2f; avg %5.2fkb per glyph\n", + double atlas_used_kb = demo_atlas_get_used (font->atlas) * sizeof (glyphy_texel_t) / 1024.; + + if (!font->num_glyphs) + return; + + LOGI ("%3d glyphs; avg curves%6.2f; avg %5.2fkb per glyph; atlas used %5.2fkb\n", font->num_glyphs, (double) font->sum_curves / font->num_glyphs, - font->sum_bytes / 1024. / font->num_glyphs); + font->sum_bytes / 1024. / font->num_glyphs, + atlas_used_kb); } From 306534df61e6e3cf11a67df48bb2fd7685a21ce5 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:32:22 -0600 Subject: [PATCH 59/68] Add bench-encode font benchmark --- bench/bench-encode.cc | 232 ++++++++++++++++++++++++++++++++++++++++++ bench/meson.build | 6 ++ meson.build | 1 + 3 files changed, 239 insertions(+) create mode 100644 bench/bench-encode.cc create mode 100644 bench/meson.build diff --git a/bench/bench-encode.cc b/bench/bench-encode.cc new file mode 100644 index 0000000..307091a --- /dev/null +++ b/bench/bench-encode.cc @@ -0,0 +1,232 @@ +/* + * Copyright 2026 Behdad Esfahbod. All Rights Reserved. + * + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct bench_stats_t { + uint64_t glyphs; + uint64_t non_empty_glyphs; + uint64_t curves; + uint64_t blob_bytes; + uint64_t outline_ns; + uint64_t encode_ns; + uint64_t wall_ns; +}; + +static void +die (const char *message) +{ + fprintf (stderr, "%s\n", message); + exit (1); +} + +static void +usage (const char *argv0) +{ + fprintf (stderr, + "Usage: %s [-r repeats] fontfile\n" + "\n" + "Encode all glyphs in a font and report outline and blob timings.\n" + "Texture upload is not measured.\n", + argv0); +} + +static bool +parse_uint (const char *arg, unsigned int *value) +{ + char *end = NULL; + unsigned long parsed = strtoul (arg, &end, 10); + + if (!arg[0] || !end || *end || parsed > UINT_MAX) + return false; + + *value = (unsigned int) parsed; + return true; +} + +static glyphy_bool_t +accumulate_curve (const glyphy_curve_t *curve, + void *user_data) +{ + std::vector *curves = (std::vector *) user_data; + curves->push_back (*curve); + return true; +} + +static double +ns_to_ms (uint64_t ns) +{ + return ns / 1000000.; +} + +static double +ns_to_us_per_glyph (uint64_t ns, uint64_t glyphs) +{ + return glyphs ? (double) ns / glyphs / 1000. : 0.; +} + +static double +glyphs_per_second (uint64_t glyphs, uint64_t ns) +{ + return ns ? glyphs * 1000000000. / ns : 0.; +} + +static double +megabytes_per_second (uint64_t bytes, uint64_t ns) +{ + return ns ? bytes * 1000000000. / ns / (1024. * 1024.) : 0.; +} + +static bench_stats_t +benchmark_font (hb_face_t *face, + unsigned int repeats) +{ + bench_stats_t stats = {}; + hb_font_t *font = hb_font_create (face); + glyphy_curve_accumulator_t *acc = glyphy_curve_accumulator_create (); + std::vector curves; + std::vector scratch_buffer (1u << 20); + unsigned int glyph_count = hb_face_get_glyph_count (face); + + if (!glyph_count) + die ("Font has no glyphs"); + + curves.reserve (1024); + + typedef std::chrono::steady_clock clock; + clock::time_point wall_start = clock::now (); + + for (unsigned int repeat = 0; repeat < repeats; repeat++) { + for (unsigned int glyph_index = 0; glyph_index < glyph_count; glyph_index++) { + unsigned int output_len = 0; + glyphy_extents_t extents; + + curves.clear (); + glyphy_curve_accumulator_reset (acc); + glyphy_curve_accumulator_set_callback (acc, accumulate_curve, &curves); + + clock::time_point outline_start = clock::now (); + glyphy_harfbuzz(font_get_glyph_shape) (font, glyph_index, acc); + clock::time_point outline_end = clock::now (); + + if (!glyphy_curve_accumulator_successful (acc)) { + char message[128]; + snprintf (message, sizeof (message), + "Failed accumulating curves for glyph %u", glyph_index); + die (message); + } + + clock::time_point encode_start = clock::now (); + if (!glyphy_curve_list_encode_blob (curves.empty () ? NULL : &curves[0], + curves.size (), + scratch_buffer.data (), + scratch_buffer.size (), + &output_len, + &extents)) { + char message[128]; + snprintf (message, sizeof (message), + "Failed encoding blob for glyph %u", glyph_index); + die (message); + } + clock::time_point encode_end = clock::now (); + + stats.glyphs++; + if (!glyphy_extents_is_empty (&extents)) + stats.non_empty_glyphs++; + stats.curves += glyphy_curve_accumulator_get_num_curves (acc); + stats.blob_bytes += (uint64_t) output_len * sizeof (glyphy_texel_t); + stats.outline_ns += std::chrono::duration_cast (outline_end - outline_start).count (); + stats.encode_ns += std::chrono::duration_cast (encode_end - encode_start).count (); + } + } + + stats.wall_ns = std::chrono::duration_cast (clock::now () - wall_start).count (); + + glyphy_curve_accumulator_destroy (acc); + hb_font_destroy (font); + + return stats; +} + +int +main (int argc, char **argv) +{ + const char *font_path = NULL; + unsigned int repeats = 1; + + for (int i = 1; i < argc; i++) { + if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "--help")) { + usage (argv[0]); + return 0; + } + if (!strcmp (argv[i], "-r") || !strcmp (argv[i], "--repeats")) { + if (++i >= argc || !parse_uint (argv[i], &repeats) || !repeats) { + usage (argv[0]); + return 1; + } + continue; + } + if (argv[i][0] == '-') { + usage (argv[0]); + return 1; + } + if (font_path) { + usage (argv[0]); + return 1; + } + font_path = argv[i]; + } + + if (!font_path) { + usage (argv[0]); + return 1; + } + + hb_blob_t *blob = hb_blob_create_from_file_or_fail (font_path); + if (!blob) + die ("Failed to open font file"); + + hb_face_t *face = hb_face_create (blob, 0); + bench_stats_t stats = benchmark_font (face, repeats); + unsigned int glyph_count = hb_face_get_glyph_count (face); + double avg_curves = stats.glyphs ? (double) stats.curves / stats.glyphs : 0.; + double avg_blob_kb = stats.glyphs ? stats.blob_bytes / 1024. / stats.glyphs : 0.; + + printf ("font: %s\n", font_path); + printf ("glyphs: %u x %u repeats = %" PRIu64 " processed (%" PRIu64 " non-empty)\n", + glyph_count, repeats, stats.glyphs, stats.non_empty_glyphs); + printf ("total blob size: %" PRIu64 " bytes (%.2fkb)\n", + stats.blob_bytes, + stats.blob_bytes / 1024.); + printf ("avg curves per glyph: %.2f\n", avg_curves); + printf ("avg blob size per glyph: %.2fkb\n", avg_blob_kb); + printf ("outline: %8.3fms total, %.3fus/glyph, %.0f glyphs/s\n", + ns_to_ms (stats.outline_ns), + ns_to_us_per_glyph (stats.outline_ns, stats.glyphs), + glyphs_per_second (stats.glyphs, stats.outline_ns)); + printf ("encode: %8.3fms total, %.3fus/glyph, %.0f glyphs/s, %.2f MiB/s\n", + ns_to_ms (stats.encode_ns), + ns_to_us_per_glyph (stats.encode_ns, stats.glyphs), + glyphs_per_second (stats.glyphs, stats.encode_ns), + megabytes_per_second (stats.blob_bytes, stats.encode_ns)); + printf ("wall: %8.3fms total (outline + encode + loop overhead)\n", + ns_to_ms (stats.wall_ns)); + + hb_face_destroy (face); + hb_blob_destroy (blob); + + return 0; +} diff --git a/bench/meson.build b/bench/meson.build new file mode 100644 index 0000000..de4944e --- /dev/null +++ b/bench/meson.build @@ -0,0 +1,6 @@ +bench_encode = executable('bench-encode', + 'bench-encode.cc', + include_directories: [confinc, srcinc], + dependencies: [harfbuzz_dep], + link_with: [libglyphy], + install: false) diff --git a/meson.build b/meson.build index a159a57..2fa5be2 100644 --- a/meson.build +++ b/meson.build @@ -95,6 +95,7 @@ configure_file(output: 'config.h', configuration: glyphy_conf) confinc = include_directories('.', 'src') subdir('src') +subdir('bench') if get_option('demo').enabled() subdir('demo') From 97d35d61962a4c57e3bad559138a83cfbdb2d863 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:40:37 -0600 Subject: [PATCH 60/68] Set default Meson buildtype to debugoptimized --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2fa5be2..39e6129 100644 --- a/meson.build +++ b/meson.build @@ -4,7 +4,8 @@ project('glyphy', 'c', 'cpp', default_options: [ 'cpp_rtti=false', 'cpp_std=c++11', - 'wrap_mode=nofallback' + 'wrap_mode=nofallback', + 'buildtype=debugoptimized', ], ) From 47239a82dad9c5269dbeb49a28a4f3fe87a78610 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:45:23 -0600 Subject: [PATCH 61/68] Cache curve bounds during blob encoding --- src/glyphy-encode.cc | 101 +++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 62 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 1c84fc0..411493a 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -61,54 +61,29 @@ quantize_fits_i16 (double v) q <= std::numeric_limits::max (); } -static double -curve_min_x (const glyphy_curve_t *c) +typedef struct { - return fmin (fmin (c->p1.x, c->p2.x), c->p3.x); -} - -static double -curve_max_x (const glyphy_curve_t *c) -{ - return fmax (fmax (c->p1.x, c->p2.x), c->p3.x); -} - -static double -curve_min_y (const glyphy_curve_t *c) -{ - return fmin (fmin (c->p1.y, c->p2.y), c->p3.y); -} - -static double -curve_max_y (const glyphy_curve_t *c) -{ - return fmax (fmax (c->p1.y, c->p2.y), c->p3.y); -} - -static void -curve_y_range (const glyphy_curve_t *c, double *min_y, double *max_y) + double min_x; + double max_x; + double min_y; + double max_y; + bool is_horizontal; + bool is_vertical; +} curve_info_t; + +static curve_info_t +curve_info (const glyphy_curve_t *c) { - *min_y = fmin (fmin (c->p1.y, c->p2.y), c->p3.y); - *max_y = fmax (fmax (c->p1.y, c->p2.y), c->p3.y); -} + curve_info_t info; -static void -curve_x_range (const glyphy_curve_t *c, double *min_x, double *max_x) -{ - *min_x = fmin (fmin (c->p1.x, c->p2.x), c->p3.x); - *max_x = fmax (fmax (c->p1.x, c->p2.x), c->p3.x); -} + info.min_x = std::min (std::min (c->p1.x, c->p2.x), c->p3.x); + info.max_x = std::max (std::max (c->p1.x, c->p2.x), c->p3.x); + info.min_y = std::min (std::min (c->p1.y, c->p2.y), c->p3.y); + info.max_y = std::max (std::max (c->p1.y, c->p2.y), c->p3.y); + info.is_horizontal = c->p1.y == c->p2.y && c->p2.y == c->p3.y; + info.is_vertical = c->p1.x == c->p2.x && c->p2.x == c->p3.x; -static bool -curve_is_horizontal (const glyphy_curve_t *c) -{ - return c->p1.y == c->p2.y && c->p2.y == c->p3.y; -} - -static bool -curve_is_vertical (const glyphy_curve_t *c) -{ - return c->p1.x == c->p2.x && c->p2.x == c->p3.x; + return info; } @@ -127,6 +102,10 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, return true; } + std::vector curve_infos (num_curves); + for (unsigned int i = 0; i < num_curves; i++) + curve_infos[i] = curve_info (&curves[i]); + /* Choose number of bands (capped at 16 per Slug paper) */ unsigned int num_hbands = std::min (num_curves, 16u); unsigned int num_vbands = std::min (num_curves, 16u); @@ -147,15 +126,15 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, std::vector> vband_curves (num_vbands); for (unsigned int i = 0; i < num_curves; i++) { + const curve_info_t &info = curve_infos[i]; + /* Horizontal lines never intersect horizontal rays; * vertical lines never intersect vertical rays. */ - if (!curve_is_horizontal (&curves[i])) { + if (!info.is_horizontal) { if (height > 0) { - double min_y, max_y; - curve_y_range (&curves[i], &min_y, &max_y); - int band_lo = (int) floor ((min_y - extents->min_y) / hband_size); - int band_hi = (int) floor ((max_y - extents->min_y) / hband_size); + int band_lo = (int) floor ((info.min_y - extents->min_y) / hband_size); + int band_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_hbands - 1); for (int b = band_lo; b <= band_hi; b++) @@ -165,12 +144,10 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } } - if (!curve_is_vertical (&curves[i])) { + if (!info.is_vertical) { if (width > 0) { - double min_x, max_x; - curve_x_range (&curves[i], &min_x, &max_x); - int band_lo = (int) floor ((min_x - extents->min_x) / vband_size); - int band_hi = (int) floor ((max_x - extents->min_x) / vband_size); + int band_lo = (int) floor ((info.min_x - extents->min_x) / vband_size); + int band_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_vbands - 1); for (int b = band_lo; b <= band_hi; b++) @@ -191,11 +168,11 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, hband_curves_asc[b] = hband_curves[b]; std::sort (hband_curves[b].begin (), hband_curves[b].end (), [&] (unsigned int a, unsigned int b) { - return curve_max_x (&curves[a]) > curve_max_x (&curves[b]); + return curve_infos[a].max_x > curve_infos[b].max_x; }); std::sort (hband_curves_asc[b].begin (), hband_curves_asc[b].end (), [&] (unsigned int a, unsigned int b) { - return curve_min_x (&curves[a]) < curve_min_x (&curves[b]); + return curve_infos[a].min_x < curve_infos[b].min_x; }); } @@ -203,11 +180,11 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, vband_curves_asc[b] = vband_curves[b]; std::sort (vband_curves[b].begin (), vband_curves[b].end (), [&] (unsigned int a, unsigned int b) { - return curve_max_y (&curves[a]) > curve_max_y (&curves[b]); + return curve_infos[a].max_y > curve_infos[b].max_y; }); std::sort (vband_curves_asc[b].begin (), vband_curves_asc[b].end (), [&] (unsigned int a, unsigned int b) { - return curve_min_y (&curves[a]) < curve_min_y (&curves[b]); + return curve_infos[a].min_y < curve_infos[b].min_y; }); } @@ -316,11 +293,11 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int best_worst = n; double best_split = (extents->min_x + extents->max_x) * 0.5; for (unsigned int ci = 0; ci < n; ci++) { - double split = curve_max_x (&curves[bc[ci]]); + double split = curve_infos[bc[ci]].max_x; unsigned int right_count = ci + 1; /* curves with max_x >= split */ unsigned int left_count = 0; for (unsigned int cj = 0; cj < n; cj++) - if (curve_min_x (&curves[bc[cj]]) <= split) + if (curve_infos[bc[cj]].min_x <= split) left_count++; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { @@ -365,11 +342,11 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int best_worst = n; double best_split = (extents->min_y + extents->max_y) * 0.5; for (unsigned int ci = 0; ci < n; ci++) { - double split = curve_max_y (&curves[bc[ci]]); + double split = curve_infos[bc[ci]].max_y; unsigned int right_count = ci + 1; unsigned int left_count = 0; for (unsigned int cj = 0; cj < n; cj++) - if (curve_min_y (&curves[bc[cj]]) <= split) + if (curve_infos[bc[cj]].min_y <= split) left_count++; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { From 09ad9f26f764caa48f6b90f973d16194d831e524 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:49:15 -0600 Subject: [PATCH 62/68] Reduce band-list allocation churn in blob encoder --- src/glyphy-encode.cc | 64 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 411493a..d5d6953 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -121,9 +121,54 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, double hband_size = height / num_hbands; double vband_size = width / num_vbands; + std::vector hband_curve_counts (num_hbands, 0); + std::vector vband_curve_counts (num_vbands, 0); + + for (unsigned int i = 0; i < num_curves; i++) { + const curve_info_t &info = curve_infos[i]; + + if (!info.is_horizontal) { + if (height > 0) { + int band_lo = (int) floor ((info.min_y - extents->min_y) / hband_size); + int band_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); + band_lo = std::max (band_lo, 0); + band_hi = std::min (band_hi, (int) num_hbands - 1); + for (int b = band_lo; b <= band_hi; b++) + hband_curve_counts[b]++; + } else { + hband_curve_counts[0]++; + } + } + + if (!info.is_vertical) { + if (width > 0) { + int band_lo = (int) floor ((info.min_x - extents->min_x) / vband_size); + int band_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); + band_lo = std::max (band_lo, 0); + band_hi = std::min (band_hi, (int) num_vbands - 1); + for (int b = band_lo; b <= band_hi; b++) + vband_curve_counts[b]++; + } else { + vband_curve_counts[0]++; + } + } + } + /* Assign curves to bands */ std::vector> hband_curves (num_hbands); + std::vector> hband_curves_asc (num_hbands); std::vector> vband_curves (num_vbands); + std::vector> vband_curves_asc (num_vbands); + + for (unsigned int b = 0; b < num_hbands; b++) { + hband_curves[b].reserve (hband_curve_counts[b]); + hband_curves_asc[b].reserve (hband_curve_counts[b]); + } + + for (unsigned int b = 0; b < num_vbands; b++) { + vband_curves[b].reserve (vband_curve_counts[b]); + vband_curves_asc[b].reserve (vband_curve_counts[b]); + } for (unsigned int i = 0; i < num_curves; i++) { const curve_info_t &info = curve_infos[i]; @@ -137,10 +182,13 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, int band_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_hbands - 1); - for (int b = band_lo; b <= band_hi; b++) + for (int b = band_lo; b <= band_hi; b++) { hband_curves[b].push_back (i); + hband_curves_asc[b].push_back (i); + } } else { hband_curves[0].push_back (i); + hband_curves_asc[0].push_back (i); } } @@ -150,10 +198,13 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, int band_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); band_lo = std::max (band_lo, 0); band_hi = std::min (band_hi, (int) num_vbands - 1); - for (int b = band_lo; b <= band_hi; b++) + for (int b = band_lo; b <= band_hi; b++) { vband_curves[b].push_back (i); + vband_curves_asc[b].push_back (i); + } } else { vband_curves[0].push_back (i); + vband_curves_asc[0].push_back (i); } } } @@ -161,11 +212,7 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, /* Build two sort orders per band for symmetric optimization. * Descending max: for rightward/upward ray (current behavior). * Ascending min: for leftward/downward ray. */ - std::vector> hband_curves_asc (num_hbands); - std::vector> vband_curves_asc (num_vbands); - for (unsigned int b = 0; b < num_hbands; b++) { - hband_curves_asc[b] = hband_curves[b]; std::sort (hband_curves[b].begin (), hband_curves[b].end (), [&] (unsigned int a, unsigned int b) { return curve_infos[a].max_x > curve_infos[b].max_x; @@ -177,7 +224,6 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } for (unsigned int b = 0; b < num_vbands; b++) { - vband_curves_asc[b] = vband_curves[b]; std::sort (vband_curves[b].begin (), vband_curves[b].end (), [&] (unsigned int a, unsigned int b) { return curve_infos[a].max_y > curve_infos[b].max_y; @@ -190,8 +236,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, /* Compute sizes -- two index lists per band */ unsigned int total_curve_indices = 0; - for (auto &band : hband_curves) total_curve_indices += band.size () * 2; - for (auto &band : vband_curves) total_curve_indices += band.size () * 2; + for (auto count : hband_curve_counts) total_curve_indices += count * 2; + for (auto count : vband_curve_counts) total_curve_indices += count * 2; unsigned int header_len = 2; /* blob header: extents + band counts */ /* Compute curve data size with shared endpoints. From 726f39ad3dfb3aabfaa5f733a184a544638ca5cb Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:53:48 -0600 Subject: [PATCH 63/68] Make band split selection linear-time --- src/glyphy-encode.cc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index d5d6953..2f08ced 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -335,16 +335,17 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, int16_t hband_split; { auto &bc = hband_curves[b]; + auto &bc_asc = hband_curves_asc[b]; unsigned int n = bc.size (); unsigned int best_worst = n; double best_split = (extents->min_x + extents->max_x) * 0.5; + unsigned int left_count = n; for (unsigned int ci = 0; ci < n; ci++) { double split = curve_infos[bc[ci]].max_x; unsigned int right_count = ci + 1; /* curves with max_x >= split */ - unsigned int left_count = 0; - for (unsigned int cj = 0; cj < n; cj++) - if (curve_infos[bc[cj]].min_x <= split) - left_count++; + while (left_count && + curve_infos[bc_asc[left_count - 1]].min_x > split) + left_count--; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { best_worst = worst; @@ -384,16 +385,17 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, int16_t vband_split; { auto &bc = vband_curves[b]; + auto &bc_asc = vband_curves_asc[b]; unsigned int n = bc.size (); unsigned int best_worst = n; double best_split = (extents->min_y + extents->max_y) * 0.5; + unsigned int left_count = n; for (unsigned int ci = 0; ci < n; ci++) { double split = curve_infos[bc[ci]].max_y; unsigned int right_count = ci + 1; - unsigned int left_count = 0; - for (unsigned int cj = 0; cj < n; cj++) - if (curve_infos[bc[cj]].min_y <= split) - left_count++; + while (left_count && + curve_infos[bc_asc[left_count - 1]].min_y > split) + left_count--; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { best_worst = worst; From 18fcebad22e90115f7887f963e0e4fa4b58d1467 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 12:56:51 -0600 Subject: [PATCH 64/68] Fold extents computation into encoder preprocessing --- src/glyphy-encode.cc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 2f08ced..af53331 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -95,17 +95,30 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int *output_len, glyphy_extents_t *extents) { - glyphy_curve_list_extents (curves, num_curves, extents); - if (num_curves == 0) { + glyphy_extents_clear (extents); *output_len = 0; return true; } std::vector curve_infos (num_curves); - for (unsigned int i = 0; i < num_curves; i++) + glyphy_extents_clear (extents); + for (unsigned int i = 0; i < num_curves; i++) { curve_infos[i] = curve_info (&curves[i]); + if (i == 0) { + extents->min_x = curve_infos[i].min_x; + extents->max_x = curve_infos[i].max_x; + extents->min_y = curve_infos[i].min_y; + extents->max_y = curve_infos[i].max_y; + } else { + extents->min_x = std::min (extents->min_x, curve_infos[i].min_x); + extents->max_x = std::max (extents->max_x, curve_infos[i].max_x); + extents->min_y = std::min (extents->min_y, curve_infos[i].min_y); + extents->max_y = std::max (extents->max_y, curve_infos[i].max_y); + } + } + /* Choose number of bands (capped at 16 per Slug paper) */ unsigned int num_hbands = std::min (num_curves, 16u); unsigned int num_vbands = std::min (num_curves, 16u); From 0c7ada9372fda42653e93fcf5eaf7f94eba99b98 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 13:00:32 -0600 Subject: [PATCH 65/68] Cache band ranges during encoder preprocessing --- src/glyphy-encode.cc | 68 +++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index af53331..2be0f39 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -69,6 +69,10 @@ typedef struct double max_y; bool is_horizontal; bool is_vertical; + int hband_lo; + int hband_hi; + int vband_lo; + int vband_hi; } curve_info_t; static curve_info_t @@ -82,6 +86,10 @@ curve_info (const glyphy_curve_t *c) info.max_y = std::max (std::max (c->p1.y, c->p2.y), c->p3.y); info.is_horizontal = c->p1.y == c->p2.y && c->p2.y == c->p3.y; info.is_vertical = c->p1.x == c->p2.x && c->p2.x == c->p3.x; + info.hband_lo = 0; + info.hband_hi = -1; + info.vband_lo = 0; + info.vband_hi = -1; return info; } @@ -138,30 +146,34 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, std::vector vband_curve_counts (num_vbands, 0); for (unsigned int i = 0; i < num_curves; i++) { - const curve_info_t &info = curve_infos[i]; + curve_info_t &info = curve_infos[i]; if (!info.is_horizontal) { if (height > 0) { - int band_lo = (int) floor ((info.min_y - extents->min_y) / hband_size); - int band_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); - band_lo = std::max (band_lo, 0); - band_hi = std::min (band_hi, (int) num_hbands - 1); - for (int b = band_lo; b <= band_hi; b++) + info.hband_lo = (int) floor ((info.min_y - extents->min_y) / hband_size); + info.hband_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); + info.hband_lo = std::max (info.hband_lo, 0); + info.hband_hi = std::min (info.hband_hi, (int) num_hbands - 1); + for (int b = info.hband_lo; b <= info.hband_hi; b++) hband_curve_counts[b]++; } else { + info.hband_lo = 0; + info.hband_hi = 0; hband_curve_counts[0]++; } } if (!info.is_vertical) { if (width > 0) { - int band_lo = (int) floor ((info.min_x - extents->min_x) / vband_size); - int band_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); - band_lo = std::max (band_lo, 0); - band_hi = std::min (band_hi, (int) num_vbands - 1); - for (int b = band_lo; b <= band_hi; b++) + info.vband_lo = (int) floor ((info.min_x - extents->min_x) / vband_size); + info.vband_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); + info.vband_lo = std::max (info.vband_lo, 0); + info.vband_hi = std::min (info.vband_hi, (int) num_vbands - 1); + for (int b = info.vband_lo; b <= info.vband_hi; b++) vband_curve_counts[b]++; } else { + info.vband_lo = 0; + info.vband_hi = 0; vband_curve_counts[0]++; } } @@ -189,36 +201,14 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, /* Horizontal lines never intersect horizontal rays; * vertical lines never intersect vertical rays. */ - if (!info.is_horizontal) { - if (height > 0) { - int band_lo = (int) floor ((info.min_y - extents->min_y) / hband_size); - int band_hi = (int) floor ((info.max_y - extents->min_y) / hband_size); - band_lo = std::max (band_lo, 0); - band_hi = std::min (band_hi, (int) num_hbands - 1); - for (int b = band_lo; b <= band_hi; b++) { - hband_curves[b].push_back (i); - hband_curves_asc[b].push_back (i); - } - } else { - hband_curves[0].push_back (i); - hband_curves_asc[0].push_back (i); - } + for (int b = info.hband_lo; b <= info.hband_hi; b++) { + hband_curves[b].push_back (i); + hband_curves_asc[b].push_back (i); } - if (!info.is_vertical) { - if (width > 0) { - int band_lo = (int) floor ((info.min_x - extents->min_x) / vband_size); - int band_hi = (int) floor ((info.max_x - extents->min_x) / vband_size); - band_lo = std::max (band_lo, 0); - band_hi = std::min (band_hi, (int) num_vbands - 1); - for (int b = band_lo; b <= band_hi; b++) { - vband_curves[b].push_back (i); - vband_curves_asc[b].push_back (i); - } - } else { - vband_curves[0].push_back (i); - vband_curves_asc[0].push_back (i); - } + for (int b = info.vband_lo; b <= info.vband_hi; b++) { + vband_curves[b].push_back (i); + vband_curves_asc[b].push_back (i); } } From 1ed83292f2abad5be9788f640ba573c43c37fab4 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 13:06:03 -0600 Subject: [PATCH 66/68] Flatten band index storage in blob encoder --- src/glyphy-encode.cc | 89 ++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 2be0f39..03a324c 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -179,22 +179,29 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } } - /* Assign curves to bands */ - std::vector> hband_curves (num_hbands); - std::vector> hband_curves_asc (num_hbands); - std::vector> vband_curves (num_vbands); - std::vector> vband_curves_asc (num_vbands); + std::vector hband_offsets (num_hbands); + std::vector vband_offsets (num_vbands); + unsigned int total_hband_indices = 0; + unsigned int total_vband_indices = 0; for (unsigned int b = 0; b < num_hbands; b++) { - hband_curves[b].reserve (hband_curve_counts[b]); - hband_curves_asc[b].reserve (hband_curve_counts[b]); + hband_offsets[b] = total_hband_indices; + total_hband_indices += hband_curve_counts[b]; } for (unsigned int b = 0; b < num_vbands; b++) { - vband_curves[b].reserve (vband_curve_counts[b]); - vband_curves_asc[b].reserve (vband_curve_counts[b]); + vband_offsets[b] = total_vband_indices; + total_vband_indices += vband_curve_counts[b]; } + /* Assign curves to bands */ + std::vector hband_curves (total_hband_indices); + std::vector hband_curves_asc (total_hband_indices); + std::vector vband_curves (total_vband_indices); + std::vector vband_curves_asc (total_vband_indices); + std::vector hband_cursors = hband_offsets; + std::vector vband_cursors = vband_offsets; + for (unsigned int i = 0; i < num_curves; i++) { const curve_info_t &info = curve_infos[i]; @@ -202,13 +209,15 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, * vertical lines never intersect vertical rays. */ for (int b = info.hband_lo; b <= info.hband_hi; b++) { - hband_curves[b].push_back (i); - hband_curves_asc[b].push_back (i); + unsigned int index = hband_cursors[b]++; + hband_curves[index] = i; + hband_curves_asc[index] = i; } for (int b = info.vband_lo; b <= info.vband_hi; b++) { - vband_curves[b].push_back (i); - vband_curves_asc[b].push_back (i); + unsigned int index = vband_cursors[b]++; + vband_curves[index] = i; + vband_curves_asc[index] = i; } } @@ -216,31 +225,33 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, * Descending max: for rightward/upward ray (current behavior). * Ascending min: for leftward/downward ray. */ for (unsigned int b = 0; b < num_hbands; b++) { - std::sort (hband_curves[b].begin (), hband_curves[b].end (), + unsigned int off = hband_offsets[b]; + unsigned int count = hband_curve_counts[b]; + std::sort (hband_curves.begin () + off, hband_curves.begin () + off + count, [&] (unsigned int a, unsigned int b) { return curve_infos[a].max_x > curve_infos[b].max_x; }); - std::sort (hband_curves_asc[b].begin (), hband_curves_asc[b].end (), + std::sort (hband_curves_asc.begin () + off, hband_curves_asc.begin () + off + count, [&] (unsigned int a, unsigned int b) { return curve_infos[a].min_x < curve_infos[b].min_x; }); } for (unsigned int b = 0; b < num_vbands; b++) { - std::sort (vband_curves[b].begin (), vband_curves[b].end (), + unsigned int off = vband_offsets[b]; + unsigned int count = vband_curve_counts[b]; + std::sort (vband_curves.begin () + off, vband_curves.begin () + off + count, [&] (unsigned int a, unsigned int b) { return curve_infos[a].max_y > curve_infos[b].max_y; }); - std::sort (vband_curves_asc[b].begin (), vband_curves_asc[b].end (), + std::sort (vband_curves_asc.begin () + off, vband_curves_asc.begin () + off + count, [&] (unsigned int a, unsigned int b) { return curve_infos[a].min_y < curve_infos[b].min_y; }); } /* Compute sizes -- two index lists per band */ - unsigned int total_curve_indices = 0; - for (auto count : hband_curve_counts) total_curve_indices += count * 2; - for (auto count : vband_curve_counts) total_curve_indices += count * 2; + unsigned int total_curve_indices = (total_hband_indices + total_vband_indices) * 2; unsigned int header_len = 2; /* blob header: extents + band counts */ /* Compute curve data size with shared endpoints. @@ -337,17 +348,16 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, * Descending sort is by max_x, so try split at each max_x boundary. */ int16_t hband_split; { - auto &bc = hband_curves[b]; - auto &bc_asc = hband_curves_asc[b]; - unsigned int n = bc.size (); + unsigned int off = hband_offsets[b]; + unsigned int n = hband_curve_counts[b]; unsigned int best_worst = n; double best_split = (extents->min_x + extents->max_x) * 0.5; unsigned int left_count = n; for (unsigned int ci = 0; ci < n; ci++) { - double split = curve_infos[bc[ci]].max_x; + double split = curve_infos[hband_curves[off + ci]].max_x; unsigned int right_count = ci + 1; /* curves with max_x >= split */ while (left_count && - curve_infos[bc_asc[left_count - 1]].min_x > split) + curve_infos[hband_curves_asc[off + left_count - 1]].min_x > split) left_count--; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { @@ -360,8 +370,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int hdr = header_len + b; unsigned int desc_off = index_offset; - for (unsigned int ci = 0; ci < hband_curves[b].size (); ci++) { - blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves[b][ci]]; + for (unsigned int ci = 0; ci < hband_curve_counts[b]; ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves[hband_offsets[b] + ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; @@ -370,15 +380,15 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int asc_off = index_offset; - for (unsigned int ci = 0; ci < hband_curves_asc[b].size (); ci++) { - blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves_asc[b][ci]]; + for (unsigned int ci = 0; ci < hband_curve_counts[b]; ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[hband_curves_asc[hband_offsets[b] + ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; index_offset++; } - blob[hdr].r = (int16_t) hband_curves[b].size (); + blob[hdr].r = (int16_t) hband_curve_counts[b]; blob[hdr].g = (int16_t) desc_off; blob[hdr].b = (int16_t) asc_off; blob[hdr].a = hband_split; @@ -387,17 +397,16 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, for (unsigned int b = 0; b < num_vbands; b++) { int16_t vband_split; { - auto &bc = vband_curves[b]; - auto &bc_asc = vband_curves_asc[b]; - unsigned int n = bc.size (); + unsigned int off = vband_offsets[b]; + unsigned int n = vband_curve_counts[b]; unsigned int best_worst = n; double best_split = (extents->min_y + extents->max_y) * 0.5; unsigned int left_count = n; for (unsigned int ci = 0; ci < n; ci++) { - double split = curve_infos[bc[ci]].max_y; + double split = curve_infos[vband_curves[off + ci]].max_y; unsigned int right_count = ci + 1; while (left_count && - curve_infos[bc_asc[left_count - 1]].min_y > split) + curve_infos[vband_curves_asc[off + left_count - 1]].min_y > split) left_count--; unsigned int worst = std::max (right_count, left_count); if (worst < best_worst) { @@ -411,8 +420,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int hdr = header_len + num_hbands + b; unsigned int desc_off = index_offset; - for (unsigned int ci = 0; ci < vband_curves[b].size (); ci++) { - blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves[b][ci]]; + for (unsigned int ci = 0; ci < vband_curve_counts[b]; ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves[vband_offsets[b] + ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; @@ -421,15 +430,15 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, unsigned int asc_off = index_offset; - for (unsigned int ci = 0; ci < vband_curves_asc[b].size (); ci++) { - blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves_asc[b][ci]]; + for (unsigned int ci = 0; ci < vband_curve_counts[b]; ci++) { + blob[index_offset].r = (int16_t) curve_texel_offset[vband_curves_asc[vband_offsets[b] + ci]]; blob[index_offset].g = 0; blob[index_offset].b = 0; blob[index_offset].a = 0; index_offset++; } - blob[hdr].r = (int16_t) vband_curves[b].size (); + blob[hdr].r = (int16_t) vband_curve_counts[b]; blob[hdr].g = (int16_t) desc_off; blob[hdr].b = (int16_t) asc_off; blob[hdr].a = vband_split; From ea696331639bf703b52245391d4b67cdcc914d1e Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 13:27:29 -0600 Subject: [PATCH 67/68] Add reusable glyph encoder object --- bench/bench-encode.cc | 15 ++++---- demo/demo-font.cc | 12 ++++--- src/glyphy-encode.cc | 80 +++++++++++++++++++++++++++++++++---------- src/glyphy.h | 21 ++++++++---- 4 files changed, 94 insertions(+), 34 deletions(-) diff --git a/bench/bench-encode.cc b/bench/bench-encode.cc index 307091a..a336de4 100644 --- a/bench/bench-encode.cc +++ b/bench/bench-encode.cc @@ -96,6 +96,7 @@ benchmark_font (hb_face_t *face, { bench_stats_t stats = {}; hb_font_t *font = hb_font_create (face); + glyphy_encoder_t *encoder = glyphy_encoder_create (); glyphy_curve_accumulator_t *acc = glyphy_curve_accumulator_create (); std::vector curves; std::vector scratch_buffer (1u << 20); @@ -130,12 +131,13 @@ benchmark_font (hb_face_t *face, } clock::time_point encode_start = clock::now (); - if (!glyphy_curve_list_encode_blob (curves.empty () ? NULL : &curves[0], - curves.size (), - scratch_buffer.data (), - scratch_buffer.size (), - &output_len, - &extents)) { + if (!glyphy_encoder_encode (encoder, + curves.empty () ? NULL : &curves[0], + curves.size (), + scratch_buffer.data (), + scratch_buffer.size (), + &output_len, + &extents)) { char message[128]; snprintf (message, sizeof (message), "Failed encoding blob for glyph %u", glyph_index); @@ -155,6 +157,7 @@ benchmark_font (hb_face_t *face, stats.wall_ns = std::chrono::duration_cast (clock::now () - wall_start).count (); + glyphy_encoder_destroy (encoder); glyphy_curve_accumulator_destroy (acc); hb_font_destroy (font); diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 68ed589..dc0cf53 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -21,6 +21,7 @@ struct demo_font_t { hb_font_t *font; glyph_cache_t *glyph_cache; demo_atlas_t *atlas; + glyphy_encoder_t *encoder; glyphy_curve_accumulator_t *acc; std::vector *scratch_buffer; @@ -39,6 +40,7 @@ demo_font_create (hb_face_t *face, font->font = hb_font_create (face); font->glyph_cache = new glyph_cache_t (); font->atlas = demo_atlas_reference (atlas); + font->encoder = glyphy_encoder_create (); font->acc = glyphy_curve_accumulator_create (); font->scratch_buffer = new std::vector (16384); @@ -51,6 +53,7 @@ demo_font_destroy (demo_font_t *font) if (!font) return; + glyphy_encoder_destroy (font->encoder); glyphy_curve_accumulator_destroy (font->acc); demo_atlas_destroy (font->atlas); delete font->scratch_buffer; @@ -102,10 +105,11 @@ encode_glyph (demo_font_t *font, if (!glyphy_curve_accumulator_successful (font->acc)) die ("Failed accumulating curves"); - if (!glyphy_curve_list_encode_blob (curves.size () ? &curves[0] : NULL, curves.size (), - buffer, buffer_len, - output_len, - extents)) + if (!glyphy_encoder_encode (font->encoder, + curves.size () ? &curves[0] : NULL, curves.size (), + buffer, buffer_len, + output_len, + extents)) die ("Failed encoding blob"); *advance = hb_font_get_glyph_h_advance (font->font, glyph_index); diff --git a/src/glyphy-encode.cc b/src/glyphy-encode.cc index 03a324c..2f3fd69 100644 --- a/src/glyphy-encode.cc +++ b/src/glyphy-encode.cc @@ -94,22 +94,55 @@ curve_info (const glyphy_curve_t *c) return info; } +struct glyphy_encoder_t +{ + std::vector curve_infos; + std::vector hband_curve_counts; + std::vector vband_curve_counts; + std::vector hband_offsets; + std::vector vband_offsets; + std::vector hband_curves; + std::vector hband_curves_asc; + std::vector vband_curves; + std::vector vband_curves_asc; + std::vector hband_cursors; + std::vector vband_cursors; + std::vector curve_texel_offset; +}; + +glyphy_encoder_t * +glyphy_encoder_create (void) +{ + return new glyphy_encoder_t; +} + +void +glyphy_encoder_destroy (glyphy_encoder_t *encoder) +{ + delete encoder; +} + glyphy_bool_t -glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, - unsigned int num_curves, - glyphy_texel_t *blob, - unsigned int blob_size, - unsigned int *output_len, - glyphy_extents_t *extents) +glyphy_encoder_encode (glyphy_encoder_t *encoder, + const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_texel_t *blob, + unsigned int blob_size, + unsigned int *output_len, + glyphy_extents_t *extents) { + if (!encoder) + return false; + if (num_curves == 0) { glyphy_extents_clear (extents); *output_len = 0; return true; } - std::vector curve_infos (num_curves); + std::vector &curve_infos = encoder->curve_infos; + curve_infos.resize (num_curves); glyphy_extents_clear (extents); for (unsigned int i = 0; i < num_curves; i++) { curve_infos[i] = curve_info (&curves[i]); @@ -142,8 +175,10 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, double hband_size = height / num_hbands; double vband_size = width / num_vbands; - std::vector hband_curve_counts (num_hbands, 0); - std::vector vband_curve_counts (num_vbands, 0); + std::vector &hband_curve_counts = encoder->hband_curve_counts; + std::vector &vband_curve_counts = encoder->vband_curve_counts; + hband_curve_counts.assign (num_hbands, 0); + vband_curve_counts.assign (num_vbands, 0); for (unsigned int i = 0; i < num_curves; i++) { curve_info_t &info = curve_infos[i]; @@ -179,8 +214,10 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } } - std::vector hband_offsets (num_hbands); - std::vector vband_offsets (num_vbands); + std::vector &hband_offsets = encoder->hband_offsets; + std::vector &vband_offsets = encoder->vband_offsets; + hband_offsets.resize (num_hbands); + vband_offsets.resize (num_vbands); unsigned int total_hband_indices = 0; unsigned int total_vband_indices = 0; @@ -195,12 +232,18 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, } /* Assign curves to bands */ - std::vector hband_curves (total_hband_indices); - std::vector hband_curves_asc (total_hband_indices); - std::vector vband_curves (total_vband_indices); - std::vector vband_curves_asc (total_vband_indices); - std::vector hband_cursors = hband_offsets; - std::vector vband_cursors = vband_offsets; + std::vector &hband_curves = encoder->hband_curves; + std::vector &hband_curves_asc = encoder->hband_curves_asc; + std::vector &vband_curves = encoder->vband_curves; + std::vector &vband_curves_asc = encoder->vband_curves_asc; + std::vector &hband_cursors = encoder->hband_cursors; + std::vector &vband_cursors = encoder->vband_cursors; + hband_curves.resize (total_hband_indices); + hband_curves_asc.resize (total_hband_indices); + vband_curves.resize (total_vband_indices); + vband_curves_asc.resize (total_vband_indices); + hband_cursors = hband_offsets; + vband_cursors = vband_offsets; for (unsigned int i = 0; i < num_curves; i++) { const curve_info_t &info = curve_infos[i]; @@ -298,7 +341,8 @@ glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, /* Pack curve data with shared endpoints. * Build curve_texel_offset[i] = texel offset for curve i's first texel. */ - std::vector curve_texel_offset (num_curves); + std::vector &curve_texel_offset = encoder->curve_texel_offset; + curve_texel_offset.resize (num_curves); unsigned int texel = curve_data_offset; for (unsigned int i = 0; i < num_curves; i++) { diff --git a/src/glyphy.h b/src/glyphy.h index 404619b..e7cb33d 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -169,6 +169,8 @@ typedef struct { short a; } glyphy_texel_t; +typedef struct glyphy_encoder_t glyphy_encoder_t; + /* * Shader source code */ @@ -180,13 +182,20 @@ GLYPHY_API const char * glyphy_vertex_shader_source (void); +GLYPHY_API glyphy_encoder_t * +glyphy_encoder_create (void); + +GLYPHY_API void +glyphy_encoder_destroy (glyphy_encoder_t *encoder); + GLYPHY_API glyphy_bool_t -glyphy_curve_list_encode_blob (const glyphy_curve_t *curves, - unsigned int num_curves, - glyphy_texel_t *blob, - unsigned int blob_size, - unsigned int *output_len, - glyphy_extents_t *extents); +glyphy_encoder_encode (glyphy_encoder_t *encoder, + const glyphy_curve_t *curves, + unsigned int num_curves, + glyphy_texel_t *blob, + unsigned int blob_size, + unsigned int *output_len, + glyphy_extents_t *extents); #ifdef __cplusplus From fb65234b31d156b573404be1ccaa5126ed2517a8 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 21 Mar 2026 15:08:01 -0600 Subject: [PATCH 68/68] Add Eric to copyright holders for the Slug shaders --- COPYING | 1 + src/glyphy-fragment.glsl | 7 ++++--- src/glyphy-vertex.glsl | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/COPYING b/COPYING index 3218c75..5eaef2e 100644 --- a/COPYING +++ b/COPYING @@ -2,6 +2,7 @@ GLyphy is licensed under the so-called "Old MIT" license, since HarfBuzz has standardized around this license: Copyright 2012 The GLyphy Project Authors +Copyright 2017 by Eric Lengyel Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this diff --git a/src/glyphy-fragment.glsl b/src/glyphy-fragment.glsl index b70eb50..504efc2 100644 --- a/src/glyphy-fragment.glsl +++ b/src/glyphy-fragment.glsl @@ -1,7 +1,8 @@ /* - * Copyright 2026 Behdad Esfahbod. All Rights Reserved. - * Based on the Slug algorithm by Eric Lengyel. - * + * Copyright 2026 Behdad Esfahbod. + * Copyright 2017 Eric Lengyel. + * Based on the Slug algorithm by Eric Lengyel: + * https://github.com/EricLengyel/Slug */ diff --git a/src/glyphy-vertex.glsl b/src/glyphy-vertex.glsl index dc65a58..50f9bb9 100644 --- a/src/glyphy-vertex.glsl +++ b/src/glyphy-vertex.glsl @@ -1,7 +1,8 @@ /* - * Copyright 2026 Behdad Esfahbod. All Rights Reserved. - * Based on the Slug algorithm by Eric Lengyel. - * + * Copyright 2026 Behdad Esfahbod. + * Copyright 2017 Eric Lengyel. + * Based on the Slug algorithm by Eric Lengyel: + * https://github.com/EricLengyel/Slug */