Commit 115e927540dba128980dd734dadeb06aa7b0f4d8

Seigo Nonaka 2023-05-19T12:11:45

[truetype] Reduce heap allocation of `deltaSet` variation data. `deltaSet` is an array of packed integers that can be 32 bits, 16 bits, or 8 bits. Before this change, these values were unpacked to 32-bit integers. However, this can cause big heap allocations, e.g., around 500 KByte for 'NotoSansCJK'. To reduce this amount, store the packed integers and unpack them just before passing to the calculation. At calculation time, due to the variable length of region indices, temporary heap allocations are necessary. This heap allocation is not negligible and visible in `ftbench` results. So, use stack-allocated arrays for short array calculations. Fixes #1230. * include/freetype/internal/ftmmtypes.h (GX_ItemVarDataRec): New fields `wordDeltaCount` and `longWords`. * src/truetype/ttgxvar.c (tt_var_load_item_variation_store): Load packed data. (tt_var_get_item_delta): Unpack data before applying.

diff --git a/include/freetype/internal/ftmmtypes.h b/include/freetype/internal/ftmmtypes.h
index b7c66c3..c4b21d6 100644
--- a/include/freetype/internal/ftmmtypes.h
+++ b/include/freetype/internal/ftmmtypes.h
@@ -28,13 +28,19 @@ FT_BEGIN_HEADER
 
   typedef struct  GX_ItemVarDataRec_
   {
-    FT_UInt            itemCount;       /* number of delta sets per item    */
-    FT_UInt            regionIdxCount;  /* number of region indices         */
-    FT_UInt*           regionIndices;   /* array of `regionCount' indices;  */
-                                        /* these index `varRegionList'      */
-    FT_ItemVarDelta*   deltaSet;        /* array of `itemCount' deltas      */
-                                        /* use `innerIndex' for this array  */
-
+    FT_UInt            itemCount;      /* Number of delta sets per item.   */
+    FT_UInt            regionIdxCount; /* Number of region indices.        */
+    FT_UInt*           regionIndices;  /* Array of `regionCount` indices;  */
+                                       /* these index `varRegionList`.     */
+    FT_Byte*           deltaSet;       /* Array of `itemCount` deltas;     */
+                                       /* use `innerIndex` for this array. */
+    FT_UShort          wordDeltaCount; /* Number of the first 32-bit ints  */
+                                       /* or 16-bit ints of `deltaSet`     */
+                                       /* depending on `longWords`.        */
+    FT_Bool            longWords;      /* If true, `deltaSet` is a 32-bit  */
+                                       /* array followed by a 16-bit       */
+                                       /* array, otherwise a 16-bit array  */
+                                       /* followed by an 8-bit array.      */
   } GX_ItemVarDataRec, *GX_ItemVarData;
 
 
diff --git a/src/truetype/ttgxvar.c b/src/truetype/ttgxvar.c
index 04caff7..8c713f1 100644
--- a/src/truetype/ttgxvar.c
+++ b/src/truetype/ttgxvar.c
@@ -509,7 +509,7 @@
     FT_UShort  axis_count;
     FT_UInt    region_count;
 
-    FT_UInt  i, j, k;
+    FT_UInt  i, j;
     FT_Bool  long_words;
 
     GX_Blend   blend           = ttface->blend;
@@ -624,6 +624,7 @@
       FT_UInt  item_count;
       FT_UInt  word_delta_count;
       FT_UInt  region_idx_count;
+      FT_UInt  per_region_size;
 
 
       if ( FT_STREAM_SEEK( offset + dataOffsetArray[i] ) )
@@ -660,6 +661,8 @@
       if ( FT_NEW_ARRAY( varData->regionIndices, region_idx_count ) )
         goto Exit;
       varData->regionIdxCount = region_idx_count;
+      varData->wordDeltaCount = word_delta_count;
+      varData->longWords      = long_words;
 
       for ( j = 0; j < varData->regionIdxCount; j++ )
       {
@@ -675,37 +678,22 @@
         }
       }
 
-      /* Parse delta set.                                                  */
-      /*                                                                   */
-      /* On input, deltas are (word_delta_count + region_idx_count) bytes  */
-      /* each if `long_words` isn't set, and twice as much otherwise.      */
-      /*                                                                   */
-      /* On output, deltas are expanded to `region_idx_count` shorts each. */
-      if ( FT_NEW_ARRAY( varData->deltaSet, item_count * region_idx_count ) )
-        goto Exit;
-      varData->itemCount = item_count;
+      per_region_size = word_delta_count + region_idx_count;
+      if ( long_words )
+        per_region_size *= 2;
 
-      for ( j = 0; j < item_count * region_idx_count; )
+      if ( FT_NEW_ARRAY( varData->deltaSet, per_region_size * item_count ) )
+        goto Exit;
+      if ( FT_Stream_Read( stream,
+                           varData->deltaSet,
+                           per_region_size * item_count ) )
       {
-        if ( long_words )
-        {
-          for ( k = 0; k < word_delta_count; k++, j++ )
-            if ( FT_READ_LONG( varData->deltaSet[j] ) )
-              goto Exit;
-          for ( ; k < region_idx_count; k++, j++ )
-            if ( FT_READ_SHORT( varData->deltaSet[j] ) )
-              goto Exit;
-        }
-        else
-        {
-          for ( k = 0; k < word_delta_count; k++, j++ )
-            if ( FT_READ_SHORT( varData->deltaSet[j] ) )
-              goto Exit;
-          for ( ; k < region_idx_count; k++, j++ )
-            if ( FT_READ_CHAR( varData->deltaSet[j] ) )
-              goto Exit;
-        }
+        FT_TRACE2(( "deltaSet read failed." ));
+        error = FT_THROW( Invalid_Table );
+        goto Exit;
       }
+
+      varData->itemCount = item_count;
     }
 
   Exit:
@@ -1005,11 +993,16 @@
     FT_Error   error  = FT_Err_Ok;
 
     GX_ItemVarData    varData;
-    FT_ItemVarDelta*  deltaSet;
+    FT_ItemVarDelta*  deltaSet = NULL;
+    FT_ItemVarDelta   deltaSetStack[16];
+
+    FT_Fixed*  scalars = NULL;
+    FT_Fixed   scalarsStack[16];
 
     FT_UInt          master, j;
-    FT_Fixed*        scalars = NULL;
-    FT_ItemVarDelta  returnValue;
+    FT_ItemVarDelta  returnValue = 0;
+    FT_UInt          per_region_size;
+    FT_Byte*         bytes;
 
 
     if ( !ttface->blend || !ttface->blend->normalizedcoords )
@@ -1026,15 +1019,48 @@
     if ( outerIndex >= itemStore->dataCount )
       return 0; /* Out of range. */
 
-    varData  = &itemStore->varData[outerIndex];
-    deltaSet = FT_OFFSET( varData->deltaSet,
-                          varData->regionIdxCount * innerIndex );
+    varData = &itemStore->varData[outerIndex];
 
     if ( innerIndex >= varData->itemCount )
       return 0; /* Out of range. */
 
-    if ( FT_QNEW_ARRAY( scalars, varData->regionIdxCount ) )
-      return 0;
+    if ( varData->regionIdxCount < 16 )
+    {
+      deltaSet = deltaSetStack;
+      scalars  = scalarsStack;
+    }
+    else
+    {
+      if ( FT_QNEW_ARRAY( deltaSet, varData->regionIdxCount ) )
+        goto Exit;
+      if ( FT_QNEW_ARRAY( scalars, varData->regionIdxCount ) )
+        goto Exit;
+    }
+
+    /* Parse delta set.                                            */
+    /*                                                             */
+    /* Deltas are (word_delta_count + region_idx_count) bytes each */
+    /* if `longWords` isn't set, and twice as much otherwise.      */
+    per_region_size = varData->wordDeltaCount + varData->regionIdxCount;
+    if ( varData->longWords )
+      per_region_size *= 2;
+
+    bytes = varData->deltaSet + per_region_size * innerIndex;
+
+    if ( varData->longWords )
+    {
+      for ( master = 0; master < varData->wordDeltaCount; master++ )
+        deltaSet[master] = FT_NEXT_LONG( bytes );
+      for ( ; master < varData->regionIdxCount; master++ )
+        deltaSet[master] = FT_NEXT_SHORT( bytes );
+    }
+    else
+    {
+      for ( master = 0; master < varData->wordDeltaCount; master++ )
+        deltaSet[master] = FT_NEXT_SHORT( bytes );
+      for ( ; master < varData->regionIdxCount; master++ )
+        deltaSet[master] = FT_NEXT_CHAR( bytes );
+    }
 
     /* outer loop steps through master designs to be blended */
     for ( master = 0; master < varData->regionIdxCount; master++ )
@@ -1109,7 +1135,11 @@
      */
     returnValue = FT_MulAddFix( scalars, deltaSet, varData->regionIdxCount );
 
-    FT_FREE( scalars );
+  Exit:
+    if ( scalars != scalarsStack )
+      FT_FREE( scalars );
+    if ( deltaSet != deltaSetStack )
+      FT_FREE( deltaSet );
 
     return returnValue;
   }