aboutsummaryrefslogtreecommitdiffstats
path: root/symbols/symbols-tool.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 14:21:39 +0100
committereunhhu <[email protected]>2026-09-27 14:21:39 +0100
commit2c1e29780da41bba13ac84ad01472d550e6d02e8 (patch)
treead24387435dffc3cde5354295584136af6720f1f /symbols/symbols-tool.c
parent2920e389f624263efe515d9a5f33f6b822efff5e (diff)
downloadvega-2c1e29780da41bba13ac84ad01472d550e6d02e8.tar.gz
vega-2c1e29780da41bba13ac84ad01472d550e6d02e8.zip
Draw text from an atlas the CPU fills, composed by the GPU
Each size's symbols are drawn on the CPU into one 8-bit atlas, every pixel the exact area its curves cover, and text is a quad per glyph reading it. A 1080p page of 11,382 glyphs composes in 1.07 ms at 16 px (it took 525 ms computing coverage per fragment), and the composed pixels are the CPU raster's exactly (worst difference 0 for DejaVu's ASCII, 1 for Noto's 11,172 Hangul syllables). Computing coverage on the GPU lost on both counts: a compute pass filling the atlas took 5-13x longer than one Cortex-A76 core (35 against 2.7 ms at 16 px), and its faster sorting-network form computed whole rows wrongly under this Mesa V3D. Coverage now has one implementation, in C, and the shaders only compose. vega_symbol_raster takes a row stride to draw into the atlas in place.
Diffstat (limited to 'symbols/symbols-tool.c')
-rw-r--r--symbols/symbols-tool.c108
1 files changed, 64 insertions, 44 deletions
diff --git a/symbols/symbols-tool.c b/symbols/symbols-tool.c
index a94feb3..b003978 100644
--- a/symbols/symbols-tool.c
+++ b/symbols/symbols-tool.c
@@ -667,7 +667,7 @@ static int compare(int argc, char **argv) {
if (need > raster_size && !(raster = realloc(raster, raster_size = need))) fail("out of memory");
double started = seconds();
vega_symbol_raster(&s, &symbol, scale, face->glyph->bitmap_left / scale, face->glyph->bitmap_top / scale,
- (int)b->width, (int)b->rows, raster);
+ (int)b->width, (int)b->rows, raster, b->width);
ours_seconds += seconds() - started;
for (unsigned j = 0; j < b->rows; ++j)
for (unsigned i2 = 0; i2 < b->width; ++i2) {
@@ -730,7 +730,7 @@ static int render(int argc, char **argv) {
int width = (int)ceil((symbol.advance > symbol.right ? symbol.advance : symbol.right) * scale) + 2;
int height = (int)ceil(pixels * 1.25) + 2;
unsigned char *raster = malloc((size_t)width * (size_t)height);
- vega_symbol_raster(&s, &symbol, scale, -1 / scale, (pixels + 1) / scale, width, height, raster);
+ vega_symbol_raster(&s, &symbol, scale, -1 / scale, (pixels + 1) / scale, width, height, raster, (size_t)width);
FILE *f = fopen(argv[5], "wb");
if (!f) fail("cannot write");
fprintf(f, "P5\n%d %d\n255\n", width, height);
@@ -775,9 +775,10 @@ static GLuint gl_target(int width, int height) {
return texture;
}
-/* Every symbol in a grid of cells, drawn by the GPU and by vega_coverage:
- * how far apart; then pages of text (glyphs cycling through the file)
- * drawn again and again: how long a page takes. */
+/* At each size: every symbol drawn into an atlas and composed by the GPU at a
+ * pen position, read back against vega_symbol_raster over the same pixels;
+ * then pages of text (glyphs cycling through the file) composed again and
+ * again. */
static int gpu(int argc, char **argv) {
if (argc < 5 || strcmp(argv[3], "--sizes")) fail("usage: vega-symbols gpu SYMBOLS.vsym --sizes 12,16,32,64 [--frames N]");
int frames = argc > 6 && !strcmp(argv[5], "--frames") ? atoi(argv[6]) : 20;
@@ -788,7 +789,7 @@ static int gpu(int argc, char **argv) {
printf("{\"renderer\":\"%s\"}\n", (const char *)glGetString(GL_RENDERER));
VegaGl gl;
double started = seconds();
- if (!vega_gl_open(&gl, &s, error, sizeof(error))) fail(error);
+ if (!vega_gl_open(&gl, error, sizeof(error))) fail(error);
glFinish();
double open_ms = (seconds() - started) * 1e3;
for (const char *p = argv[4]; *p;) {
@@ -796,72 +797,91 @@ static int gpu(int argc, char **argv) {
long size = strtol(p, &end, 10);
p = *end == ',' ? end + 1 : end;
if (size <= 0 || size > 512) fail("--sizes");
- double scale = (double)size / s.units;
- /* Cells that hold any symbol, so none reaches into another's. */
+ float scale = (float)size / s.units;
+ VegaAtlas atlas;
+ started = seconds();
+ if (!vega_gl_atlas(&gl, &s, scale, NULL, 0, &atlas, error, sizeof(error))) {
+ printf("{\"pixels_per_em\":%ld,\"error\":\"%s\"}\n", size, error);
+ continue;
+ }
+ glFinish();
+ double fill_ms = (seconds() - started) * 1e3;
+ /* Every symbol composed at a pen position of its own cell, read back
+ * and set against vega_symbol_raster at the same pixels. */
int left = 0, bottom = 0, right = 0, top = 0;
for (uint32_t i = 0; i < s.symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(&s, i);
- left = symbol.left < left ? symbol.left : left;
- bottom = symbol.bottom < bottom ? symbol.bottom : bottom;
- right = symbol.right > right ? symbol.right : right;
- top = symbol.top > top ? symbol.top : top;
+ VegaPlace place = atlas.places[i];
+ if (!place.width) continue;
+ left = place.left < left ? place.left : left;
+ top = place.top > top ? place.top : top;
+ right = place.left + place.width > right ? place.left + place.width : right;
+ bottom = place.top - place.height < bottom ? place.top - place.height : bottom;
}
- int cell = (int)ceil((right - left > top - bottom ? right - left : top - bottom) * scale) + 4;
- int across = 4096 / cell, rows = (int)((s.symbols + (uint32_t)across - 1) / (uint32_t)across);
- if (rows * cell > 4096) rows = 4096 / cell;
- int width = across * cell, height = rows * cell;
+ int cell_width = right - left + 2, cell_height = top - bottom + 2;
+ int across = 2048 / cell_width, rows = (int)((s.symbols + (uint32_t)across - 1) / (uint32_t)across);
+ if (rows * cell_height > 4096) rows = 4096 / cell_height;
+ int grid_width = across * cell_width, grid_height = rows * cell_height;
uint32_t shown = s.symbols < (uint32_t)(across * rows) ? s.symbols : (uint32_t)(across * rows);
- VegaGlyph *glyphs = malloc(shown * sizeof(*glyphs));
+ VegaGlyph *cells = malloc((shown ? shown : 1) * sizeof(*cells));
for (uint32_t i = 0; i < shown; ++i)
- glyphs[i] = (VegaGlyph){(float)(i % (uint32_t)across * (uint32_t)cell) + 2 - (float)(left * scale),
- (float)(i / (uint32_t)across * (uint32_t)cell) + 2 + (float)(top * scale), (float)scale, i};
- GLuint texture = gl_target(width, height);
- vega_gl_draw(&gl, &s, glyphs, shown, width, height);
- unsigned char *drawn = malloc((size_t)width * (size_t)height), *reference = malloc((size_t)cell * (size_t)cell);
+ cells[i] = (VegaGlyph){(int)(i % (uint32_t)across) * cell_width + 1 - left,
+ (int)(i / (uint32_t)across) * cell_height + 1 + top, i};
+ GLuint grid = gl_target(grid_width, grid_height);
+ vega_gl_draw(&gl, &atlas, cells, shown, grid_width, grid_height);
+ unsigned char *drawn = malloc((size_t)grid_width * (size_t)grid_height), *reference = NULL;
+ size_t reference_size = 0;
glPixelStorei(GL_PACK_ALIGNMENT, 1);
- glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, drawn);
+ glReadPixels(0, 0, grid_width, grid_height, GL_RED, GL_UNSIGNED_BYTE, drawn);
+ glDeleteTextures(1, &grid);
double total = 0;
uint64_t pixels = 0;
int worst = 0;
+ size_t need = (size_t)cell_width * (size_t)cell_height;
+ if (!(reference = malloc(need))) fail("out of memory");
+ reference_size = need;
for (uint32_t i = 0; i < shown; ++i) {
VegaSymbol symbol = vega_symbol_at(&s, i);
- int x0 = (int)(i % (uint32_t)across) * cell, y0 = (int)(i / (uint32_t)across) * cell;
- vega_symbol_raster(&s, &symbol, scale, (x0 - glyphs[i].x) / scale, (glyphs[i].y - y0) / scale, cell, cell, reference);
- for (int j = 0; j < cell; ++j)
- for (int k = 0; k < cell; ++k) {
+ int x0 = (int)(i % (uint32_t)across) * cell_width, y0 = (int)(i / (uint32_t)across) * cell_height;
+ /* the cell's pixels, relative to the pen: from (x0 - pen x, pen y - y0) */
+ vega_symbol_raster(&s, &symbol, scale, (x0 - cells[i].x) / scale, (cells[i].y - y0) / scale, cell_width,
+ cell_height, reference, (size_t)cell_width);
+ for (int j = 0; j < cell_height; ++j)
+ for (int k = 0; k < cell_width; ++k) {
/* GL's rows run bottom up */
- int gpu_value = drawn[(size_t)(height - 1 - (y0 + j)) * (size_t)width + (size_t)(x0 + k)];
- int difference = abs(gpu_value - reference[j * cell + k]);
+ int gpu_value = drawn[(size_t)(grid_height - 1 - (y0 + j)) * (size_t)grid_width + (size_t)(x0 + k)];
+ int difference = abs(gpu_value - reference[(size_t)j * (size_t)cell_width + (size_t)k]);
total += difference;
worst = difference > worst ? difference : worst;
++pixels;
}
}
- /* Pages: glyphs at their advances in lines, a full target's worth. */
+ (void)reference_size;
+ free(cells);
+ /* Pages: glyphs at their advances in lines, a 1920x1080 target's worth. */
int page_width = 1920, page_height = 1080;
uint32_t page = 0, capacity = 1 << 16;
VegaGlyph *text = malloc(capacity * sizeof(*text));
- float x = 0, line = (float)size * 1.2f;
- for (float y = line; y + (float)size * 0.3f < (float)page_height && page < capacity; ) {
- VegaSymbol symbol = vega_symbol_at(&s, page % s.symbols);
- float advance = (float)(symbol.advance * scale);
- if (x + advance > (float)page_width) { x = 0; y += line; continue; }
- text[page++] = (VegaGlyph){x, y, (float)scale, page % s.symbols};
+ int x = 0, line = (int)(size * 1.2);
+ for (int y = line; y + size / 3 < page_height && page < capacity;) {
+ uint32_t index = page % s.symbols;
+ int advance = (int)lroundf(vega_symbol_at(&s, index).advance * scale);
+ if (x + advance > page_width) { x = 0; y += line; continue; }
+ text[page++] = (VegaGlyph){x, y, index};
x += advance;
}
- glDeleteTextures(1, &texture);
- texture = gl_target(page_width, page_height);
- vega_gl_draw(&gl, &s, text, page, page_width, page_height);
+ GLuint texture = gl_target(page_width, page_height);
+ vega_gl_draw(&gl, &atlas, text, page, page_width, page_height);
glFinish();
started = seconds();
- for (int f = 0; f < frames; ++f) vega_gl_draw(&gl, &s, text, page, page_width, page_height);
+ for (int f = 0; f < frames; ++f) vega_gl_draw(&gl, &atlas, text, page, page_width, page_height);
glFinish();
double page_ms = (seconds() - started) / frames * 1e3;
- printf("{\"pixels_per_em\":%ld,\"symbols\":%u,\"mean_difference\":%.3f,\"worst\":%d,\"page_glyphs\":%u,"
- "\"page_ms\":%.2f,\"upload_ms\":%.1f}\n", size, shown, pixels ? total / (double)pixels : 0, worst, page, page_ms, open_ms);
+ printf("{\"pixels_per_em\":%ld,\"symbols\":%u,\"mean_difference\":%.3f,\"worst\":%d,\"atlas\":\"%dx%d\","
+ "\"fill_ms\":%.1f,\"page_glyphs\":%u,\"page_ms\":%.2f,\"upload_ms\":%.1f}\n", size, shown,
+ pixels ? total / (double)pixels : 0, worst, atlas.width, atlas.height, fill_ms, page, page_ms, open_ms);
glDeleteTextures(1, &texture);
+ vega_gl_atlas_close(&atlas);
free(text);
- free(glyphs);
free(drawn);
free(reference);
}