From 4fbf7dcc4cae525bf716746513ed1ff1c24482ff Mon Sep 17 00:00:00 2001 From: Alexei Podtelezhnikov Date: Tue, 5 May 2026 20:07:31 -0400 Subject: [PATCH] [smooth] Dynamic pool allocation. The fixed pool allocation on stack is very fast and works very well for rendering glyphs smaller than 40 pixels. Larger glyphs have to be split and rendered piecewise, which is slower. This commit introduces dynamic pool allocation for larger glyphs. Complex large glyphs are now rendered about 2x faster. * src/smooth/ftgrays.c (gray_convert_glyph): Use simpler banding schema in case of rendering emergency. (gray_raster_render): Allocate larger pools dynamically. --- src/smooth/ftgrays.c | 154 +++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 70 deletions(-) diff --git a/src/smooth/ftgrays.c b/src/smooth/ftgrays.c index 0f7b63937..dcc875602 100644 --- a/src/smooth/ftgrays.c +++ b/src/smooth/ftgrays.c @@ -492,9 +492,10 @@ typedef ptrdiff_t FT_PtrDist; TCoord count_ey; /* same as (max_ey - min_ey) */ int error; /* pool overflow exception */ + PCell buffer; /* buffer */ + PCell cell_null; /* last cell, used as dumpster and limit */ PCell cell; /* current cell */ PCell cell_free; /* call allocation next free slot */ - PCell cell_null; /* last cell, used as dumpster and limit */ PCell* ycells; /* array of cell linked-lists; one per */ /* vertical coordinate in the current band */ @@ -1861,10 +1862,6 @@ typedef ptrdiff_t FT_PtrDist; static int gray_convert_glyph( RAS_ARG ) { - TCell buffer[FT_MAX_GRAY_POOL]; - size_t height = (size_t)( ras.cbox.yMax - ras.cbox.yMin ); - size_t n = FT_MAX_GRAY_POOL / 8; - TCoord y; TCoord bands[32]; /* enough to accommodate bisections */ TCoord* band; @@ -1873,89 +1870,74 @@ typedef ptrdiff_t FT_PtrDist; /* Initialize the null cell at the end of the poll. */ - ras.cell_null = buffer + FT_MAX_GRAY_POOL - 1; ras.cell_null->x = CELL_MAX_X_VALUE; ras.cell_null->area = 0; ras.cell_null->cover = 0; ras.cell_null->next = NULL; /* set up vertical bands */ - ras.ycells = (PCell*)buffer; + ras.ycells = (PCell*)ras.buffer; - if ( height > n ) + ras.min_ex = ras.cbox.xMin; + ras.max_ex = ras.cbox.xMax; + + band = bands; + band[1] = ras.cbox.yMin; + band[0] = ras.cbox.yMax; + + do { - /* two divisions rounded up */ - n = ( height + n - 1 ) / n; - height = ( height + n - 1 ) / n; - } + size_t n; + TCoord i; - for ( y = ras.cbox.yMin; y < ras.cbox.yMax; ) - { - ras.min_ey = y; - y += height; - ras.max_ey = FT_MIN( y, ras.cbox.yMax ); + ras.min_ey = band[1]; + ras.max_ey = band[0]; ras.count_ey = ras.max_ey - ras.min_ey; - band = bands; - band[1] = ras.cbox.xMin; - band[0] = ras.cbox.xMax; + /* memory management: zero out and skip ycells */ + for ( i = 0; i < ras.count_ey; ++i ) + ras.ycells[i] = ras.cell_null; - do + n = ( (size_t)ras.count_ey * sizeof ( PCell ) + sizeof ( TCell ) - 1 ) + / sizeof ( TCell ); + + ras.cell_free = ras.buffer + n; + ras.cell = ras.cell_null; + ras.error = Smooth_Err_Ok; + + error = gray_convert_glyph_inner( RAS_VAR_ continued ); + continued = 1; + + if ( !error ) { - TCoord i; + if ( ras.render_span ) /* for FT_RASTER_FLAG_DIRECT only */ + gray_sweep_direct( RAS_VAR ); + else + gray_sweep( RAS_VAR ); + band--; + continue; + } + else if ( error != Smooth_Err_Raster_Overflow ) + goto Exit; + /* render pool overflow; we will reduce the render band by half */ + i = ( band[0] - band[1] ) >> 1; - ras.min_ex = band[1]; - ras.max_ex = band[0]; + /* this should never happen even with tiny rendering pool */ + if ( i == 0 ) + { + FT_TRACE7(( "gray_convert_glyph: rotten glyph\n" )); + error = FT_THROW( Raster_Overflow ); + goto Exit; + } - /* memory management: zero out and skip ycells */ - for ( i = 0; i < ras.count_ey; ++i ) - ras.ycells[i] = ras.cell_null; - - n = ( (size_t)ras.count_ey * sizeof ( PCell ) + sizeof ( TCell ) - 1 ) - / sizeof ( TCell ); - - ras.cell_free = buffer + n; - ras.cell = ras.cell_null; - ras.error = Smooth_Err_Ok; - - error = gray_convert_glyph_inner( RAS_VAR_ continued ); - continued = 1; - - if ( !error ) - { - if ( ras.render_span ) /* for FT_RASTER_FLAG_DIRECT only */ - gray_sweep_direct( RAS_VAR ); - else - gray_sweep( RAS_VAR ); - band--; - continue; - } - else if ( error != Smooth_Err_Raster_Overflow ) - goto Exit; - - /* render pool overflow; we will reduce the render band by half */ - i = ( band[0] - band[1] ) >> 1; - - /* this should never happen even with tiny rendering pool */ - if ( i == 0 ) - { - FT_TRACE7(( "gray_convert_glyph: rotten glyph\n" )); - error = FT_THROW( Raster_Overflow ); - goto Exit; - } - - band++; - band[1] = band[0]; - band[0] += i; - } while ( band >= bands ); - } + band++; + band[1] = band[0]; + band[0] += i; + } while ( band >= bands ); Exit: - ras.cell = ras.cell_free = ras.cell_null = NULL; - ras.ycells = NULL; - return error; } @@ -1964,6 +1946,9 @@ typedef ptrdiff_t FT_PtrDist; gray_raster_render( FT_Raster raster, const FT_Raster_Params* params ) { + FT_Long estimate; + int ret; + const FT_Outline* outline = (const FT_Outline*)params->source; const FT_Bitmap* target_map = params->target; @@ -2039,7 +2024,36 @@ typedef ptrdiff_t FT_PtrDist; if ( ras.cbox.xMin >= ras.cbox.xMax || ras.cbox.yMin >= ras.cbox.yMax ) return Smooth_Err_Ok; - return gray_convert_glyph( RAS_VAR ); + /* allocate memory based on empirical estimate from CJK fonts */ + estimate = ( ras.cbox.xMax - ras.cbox.xMin + + ras.cbox.yMax - ras.cbox.yMin ) * 10; + if ( estimate > FT_MAX_GRAY_POOL ) + { + FT_Error error; + FT_Memory memory = (FT_Memory)((gray_PRaster)raster)->memory; + + + if ( FT_QNEW_ARRAY( ras.buffer, estimate ) ) + ret = error; + else + { + ras.cell_null = ras.buffer + estimate - 1; + ret = gray_convert_glyph( RAS_VAR ); + FT_FREE( ras.buffer ); + } + } + else + { + TCell buffer[FT_MAX_GRAY_POOL]; /* stack allocation */ + + + ras.buffer = buffer; + ras.cell_null = ras.buffer + FT_MAX_GRAY_POOL - 1; + + ret = gray_convert_glyph( RAS_VAR ); + } + + return ret; }