From 20477a9ae44e2a0ba976f01434d7fe8a0aeb0220 Mon Sep 17 00:00:00 2001 From: Daniel Gredler Date: Sat, 2 Aug 2025 15:41:53 +0200 Subject: [PATCH 1/2] Improve caching in macOS CCharToGlyphMapper --- .../classes/sun/font/CCharToGlyphMapper.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java b/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java index f94b8968614d6..f5e8c8666a2a7 100644 --- a/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java +++ b/src/java.desktop/macosx/classes/sun/font/CCharToGlyphMapper.java @@ -33,9 +33,10 @@ public final class CCharToGlyphMapper extends CharToGlyphMapper { private static native int countGlyphs(final long nativeFontPtr); - private Cache cache = new Cache(); - CFont fFont; - int numGlyphs = -1; + private final Cache rawCache = new Cache(true); + private final Cache modCache = new Cache(false); + private final CFont fFont; + private int numGlyphs = -1; public CCharToGlyphMapper(CFont font) { fFont = font; @@ -101,13 +102,18 @@ public synchronized int charToGlyph(char unicode) { } private int charToGlyph(char unicode, boolean raw) { - int glyph = cache.get(unicode, raw); + Cache cache = raw ? rawCache : modCache; + int glyph = cache.get(unicode); if (glyph != 0) return glyph; - final char[] unicodeArray = new char[] { unicode }; - final int[] glyphArray = new int[1]; - nativeCharsToGlyphs(fFont.getNativeFontPtr(), 1, unicodeArray, glyphArray); - glyph = glyphArray[0]; + if (isIgnorableWhitespace(unicode) || (isDefaultIgnorable(unicode) && !raw)) { + glyph = INVISIBLE_GLYPH_ID; + } else { + final char[] unicodeArray = new char[]{unicode}; + final int[] glyphArray = new int[1]; + nativeCharsToGlyphs(fFont.getNativeFontPtr(), 1, unicodeArray, glyphArray); + glyph = glyphArray[0]; + } cache.put(unicode, glyph); @@ -131,7 +137,8 @@ private int charToGlyph(int unicode, boolean raw) { int base = unicode - 0x10000; surrogates[0] = (char)((base >>> 10) + HI_SURROGATE_START); surrogates[1] = (char)((base % 0x400) + LO_SURROGATE_START); - cache.get(2, surrogates, glyphs, raw); + Cache cache = raw ? rawCache : modCache; + cache.get(2, surrogates, glyphs); return glyphs[0]; } else { return charToGlyph((char) unicode, raw); @@ -140,7 +147,7 @@ private int charToGlyph(int unicode, boolean raw) { @Override public synchronized void charsToGlyphs(int count, char[] unicodes, int[] glyphs) { - cache.get(count, unicodes, glyphs, false); + modCache.get(count, unicodes, glyphs); } @Override @@ -164,20 +171,18 @@ private final class Cache { private static final int FIRST_LAYER_SIZE = 256; private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 + private final boolean raw; private final int[] firstLayerCache = new int[FIRST_LAYER_SIZE]; private SparseBitShiftingTwoLayerArray secondLayerCache; private HashMap generalCache; - Cache() { + Cache(boolean raw) { + this.raw = raw; // need to prevent getting '-1' stuck in the cache firstLayerCache[1] = 1; } - public synchronized int get(final int index, final boolean raw) { - if (isIgnorableWhitespace(index) || (isDefaultIgnorable(index) && !raw)) { - return INVISIBLE_GLYPH_ID; - } - + public synchronized int get(final int index) { if (index < FIRST_LAYER_SIZE) { // catch common glyphcodes return firstLayerCache[index]; @@ -248,7 +253,7 @@ public void put(final int index, final int value) { } } - public synchronized void get(int count, char[] indices, int[] values, boolean raw) + public synchronized void get(int count, char[] indices, int[] values) { // "missed" is the count of 'char' that are not mapped. // Surrogates count for 2. @@ -270,13 +275,16 @@ public synchronized void get(int count, char[] indices, int[] values, boolean ra } } - final int value = get(code, raw); + final int value = get(code); if (value != 0 && value != -1) { values[i] = value; if (code >= 0x10000) { values[i+1] = INVISIBLE_GLYPH_ID; i++; } + } else if (isIgnorableWhitespace(code) || (isDefaultIgnorable(code) && !raw)) { + values[i] = INVISIBLE_GLYPH_ID; + put(code, INVISIBLE_GLYPH_ID); } else { values[i] = 0; put(code, -1); From fbbadb94e92e728417b7d6989119476d75a90a91 Mon Sep 17 00:00:00 2001 From: Daniel Gredler Date: Tue, 5 Aug 2025 23:07:40 +0100 Subject: [PATCH 2/2] Improve caching in CompositeGlyphMapper --- .../sun/font/CompositeGlyphMapper.java | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java index 673e2eb0d9ffd..dcf0bab3ab37a 100644 --- a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java +++ b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java @@ -25,7 +25,7 @@ package sun.font; -/* remember that the API requires a Font use a +/* Remember that the API requires a Font use a * consistent glyph id. for a code point, and this is a * problem if a particular strike uses native scaler sometimes * and the JDK scaler others. That needs to be dealt with somewhere, but @@ -36,9 +36,9 @@ * to the maximum surrogate pair code point. * This implementation will not cache as much, since the storage * requirements are not justifiable. Even so it still can use up - * to 216*256*4 bytes of storage per composite font. If an app + * to 2*216*256*4 bytes of storage per composite font. If an app * calls canDisplay on this range for all 20 composite fonts that's - * over 1Mb of cached data. May need to employ WeakReferences if + * over 2Mb of cached data. May need to employ WeakReferences if * this appears to cause problems. */ @@ -54,14 +54,20 @@ public class CompositeGlyphMapper extends CharToGlyphMapper { public static final int BLOCKSZ = 256; public static final int MAXUNICODE = NBLOCKS*BLOCKSZ; - CompositeFont font; - CharToGlyphMapper[] slotMappers; - int[][] glyphMaps; - private boolean hasExcludes; + private final CompositeFont font; + private final CharToGlyphMapper[] slotMappers; + private final int[][] glyphMapsRaw; + private final int[][] glyphMapsMod; + private final boolean hasExcludes; public CompositeGlyphMapper(CompositeFont compFont) { font = compFont; - initMapper(); + glyphMapsRaw = new int[NBLOCKS][]; + glyphMapsMod = new int[NBLOCKS][]; + slotMappers = new CharToGlyphMapper[font.numSlots]; + /* This requires that slot 0 is never empty. */ + missingGlyph = font.getSlotFont(0).getMissingGlyphCode(); + missingGlyph = compositeGlyphCode(0, missingGlyph); /* This is often false which saves the overhead of a * per-mapped char method call. */ @@ -73,34 +79,24 @@ public int compositeGlyphCode(int slot, int glyphCode) { return (slot << 24 | (glyphCode & GLYPHMASK)); } - private void initMapper() { - if (missingGlyph == CharToGlyphMapper.UNINITIALIZED_GLYPH) { - if (glyphMaps == null) { - glyphMaps = new int[NBLOCKS][]; - } - slotMappers = new CharToGlyphMapper[font.numSlots]; - /* This requires that slot 0 is never empty. */ - missingGlyph = font.getSlotFont(0).getMissingGlyphCode(); - missingGlyph = compositeGlyphCode(0, missingGlyph); - } - } - - private int getCachedGlyphCode(int unicode) { + private int getCachedGlyphCode(int unicode, boolean raw) { if (unicode >= MAXUNICODE) { return UNINITIALIZED_GLYPH; // don't cache surrogates } int[] gmap; + int[][] glyphMaps = raw ? glyphMapsRaw : glyphMapsMod; if ((gmap = glyphMaps[unicode >> 8]) == null) { return UNINITIALIZED_GLYPH; } return gmap[unicode & 0xff]; } - private void setCachedGlyphCode(int unicode, int glyphCode) { + private void setCachedGlyphCode(int unicode, int glyphCode, boolean raw) { if (unicode >= MAXUNICODE) { return; // don't cache surrogates } int index0 = unicode >> 8; + int[][] glyphMaps = raw ? glyphMapsRaw : glyphMapsMod; if (glyphMaps[index0] == null) { glyphMaps[index0] = new int[BLOCKSZ]; for (int i=0;i