[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.

* include/freetype/config/ftoption.h: Explain the render pool size.
* devel/ftoption.h: Ditto.
This commit is contained in:
Alexei Podtelezhnikov 2026-05-08 18:14:53 -04:00
parent a1bbf07413
commit a39ef7632e
3 changed files with 96 additions and 74 deletions

View file

@ -439,8 +439,12 @@ FT_BEGIN_HEADER
/**************************************************************************
*
* The size in bytes of the render pool used by the scan-line converter to
* do all of its work.
* The size in bytes of the stack render pool used by the scan-line
* converters. Use this option to limit the stack usage. The memory
* requirements are proportional to size and complexity of a given glyph.
* FreeType's anti-aliased render switches to dynamic heap allocations
* when necessary. The bi-level converter subdivides the glyph at some
* performance cost.
*/
#define FT_RENDER_POOL_SIZE 16384L

View file

@ -439,8 +439,12 @@ FT_BEGIN_HEADER
/**************************************************************************
*
* The size in bytes of the render pool used by the scan-line converter to
* do all of its work.
* The size in bytes of the stack render pool used by the scan-line
* converters. Use this option to limit the stack usage. The memory
* requirements are proportional to size and complexity of a given glyph.
* FreeType's anti-aliased render switches to dynamic heap allocations
* when necessary. The bi-level converter subdivides the glyph at some
* performance cost.
*/
#define FT_RENDER_POOL_SIZE 16384L

View file

@ -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;
}