1 /*
2  * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.font;
27 
28 import java.awt.GraphicsConfiguration;
29 import java.awt.GraphicsEnvironment;
30 import java.lang.ref.Reference;
31 import java.lang.ref.ReferenceQueue;
32 import java.lang.ref.SoftReference;
33 import java.lang.ref.WeakReference;
34 import java.util.*;
35 
36 import sun.java2d.Disposer;
37 import sun.java2d.pipe.BufferedContext;
38 import sun.java2d.pipe.RenderQueue;
39 import sun.java2d.pipe.hw.AccelGraphicsConfig;
40 import jdk.internal.misc.Unsafe;
41 
42 /**
43 
44 A FontStrike is the keeper of scaled glyph image data which is expensive
45 to compute so needs to be cached.
46 So long as that data may be being used it cannot be invalidated.
47 Yet we also need to limit the amount of native memory and number of
48 strike objects in use.
49 For scaleability and ease of use, a key goal is multi-threaded read
50 access to a strike, so that it may be shared by multiple client objects,
51 potentially executing on different threads, with no special reference
52 counting or "check-out/check-in" requirements which would pass on the
53 burden of keeping track of strike references to the SG2D and other clients.
54 
55 A cache of strikes is maintained via Reference objects.
56 This helps in two ways :
57 1. The VM will free references when memory is low or they have not been
58 used in a long time.
59 2. Reference queues provide a way to get notification of this so we can
60 free native memory resources.
61 
62  */
63 
64 @SuppressWarnings("removal")
65 public final class StrikeCache {
66 
67     static final Unsafe unsafe = Unsafe.getUnsafe();
68 
69     static ReferenceQueue<Object> refQueue = Disposer.getQueue();
70 
71     static ArrayList<GlyphDisposedListener> disposeListeners = new ArrayList<GlyphDisposedListener>(1);
72 
73 
74     /* Reference objects may have their referents cleared when GC chooses.
75      * During application client start-up there is typically at least one
76      * GC which causes the hotspot VM to clear soft (not just weak) references
77      * Thus not only is there a GC pause, but the work done do rasterise
78      * glyphs that are fairly certain to be needed again almost immediately
79      * is thrown away. So for performance reasons a simple optimisation is to
80      * keep up to 8 strong references to strikes to reduce the chance of
81      * GC'ing strikes that have been used recently. Note that this may not
82      * suffice in Solaris UTF-8 locales where a single composite strike may be
83      * composed of 15 individual strikes, plus the composite strike.
84      * And this assumes the new architecture doesn't maintain strikes for
85      * natively accessed bitmaps. It may be worth "tuning" the number of
86      * strikes kept around for the platform or locale.
87      * Since no attempt is made to ensure uniqueness or ensure synchronized
88      * access there is no guarantee that this cache will ensure that unique
89      * strikes are cached. Every time a strike is looked up it is added
90      * to the current index in this cache. All this cache has to do to be
91      * worthwhile is prevent excessive cache flushing of strikes that are
92      * referenced frequently. The logic that adds references here could be
93      * tweaked to keep only strikes  that represent untransformed, screen
94      * sizes as that's the typical performance case.
95      */
96     static int MINSTRIKES = 8; // can be overridden by property
97     static int recentStrikeIndex = 0;
98     static FontStrike[] recentStrikes;
99     static boolean cacheRefTypeWeak;
100 
101     /*
102      * Native sizes and offsets for glyph cache
103      * There are 10 values.
104      */
105     static int nativeAddressSize;
106     static int glyphInfoSize;
107     static int xAdvanceOffset;
108     static int yAdvanceOffset;
109     static int boundsOffset;
110     static int widthOffset;
111     static int heightOffset;
112     static int rowBytesOffset;
113     static int topLeftXOffset;
114     static int topLeftYOffset;
115     static int pixelDataOffset;
116     static int cacheCellOffset;
117     static int managedOffset;
118     static long invisibleGlyphPtr;
119 
120     /* Native method used to return information used for unsafe
121      * access to native data.
122      * return values as follows:-
123      * arr[0] = size of an address/pointer.
124      * arr[1] = size of a GlyphInfo
125      * arr[2] = offset of advanceX
126      * arr[3] = offset of advanceY
127      * arr[4] = offset of width
128      * arr[5] = offset of height
129      * arr[6] = offset of rowBytes
130      * arr[7] = offset of topLeftX
131      * arr[8] = offset of topLeftY
132      * arr[9] = offset of pixel data.
133      * arr[10] = address of a GlyphImageRef representing the invisible glyph
134      */
getGlyphCacheDescription(long[] infoArray)135     static native void getGlyphCacheDescription(long[] infoArray);
136 
137     static {
138 
139         long[] nativeInfo = new long[13];
140         getGlyphCacheDescription(nativeInfo);
141         //Can also get address size from Unsafe class :-
142         //nativeAddressSize = unsafe.addressSize();
143         nativeAddressSize = (int)nativeInfo[0];
144         glyphInfoSize     = (int)nativeInfo[1];
145         xAdvanceOffset    = (int)nativeInfo[2];
146         yAdvanceOffset    = (int)nativeInfo[3];
147         widthOffset       = (int)nativeInfo[4];
148         heightOffset      = (int)nativeInfo[5];
149         rowBytesOffset    = (int)nativeInfo[6];
150         topLeftXOffset    = (int)nativeInfo[7];
151         topLeftYOffset    = (int)nativeInfo[8];
152         pixelDataOffset   = (int)nativeInfo[9];
153         invisibleGlyphPtr = nativeInfo[10];
154         cacheCellOffset = (int) nativeInfo[11];
155         managedOffset = (int) nativeInfo[12];
156 
157         if (nativeAddressSize < 4) {
158             throw new InternalError("Unexpected address size for font data: " +
159                                     nativeAddressSize);
160         }
161 
java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Object>() { public Object run() { String refType = System.getProperty(R, R); cacheRefTypeWeak = refType.equals(R); String minStrikesStr = System.getProperty(R); if (minStrikesStr != null) { try { MINSTRIKES = Integer.parseInt(minStrikesStr); if (MINSTRIKES <= 0) { MINSTRIKES = 1; } } catch (NumberFormatException e) { } } recentStrikes = new FontStrike[MINSTRIKES]; return null; } })162         java.security.AccessController.doPrivileged(
163                                     new java.security.PrivilegedAction<Object>() {
164             public Object run() {
165 
166                /* Allow a client to override the reference type used to
167                 * cache strikes. The default is "soft" which hints to keep
168                 * the strikes around. This property allows the client to
169                 * override this to "weak" which hint to the GC to free
170                 * memory more aggressively.
171                 */
172                String refType =
173                    System.getProperty("sun.java2d.font.reftype", "soft");
174                cacheRefTypeWeak = refType.equals("weak");
175 
176                 String minStrikesStr =
177                     System.getProperty("sun.java2d.font.minstrikes");
178                 if (minStrikesStr != null) {
179                     try {
180                         MINSTRIKES = Integer.parseInt(minStrikesStr);
181                         if (MINSTRIKES <= 0) {
182                             MINSTRIKES = 1;
183                         }
184                     } catch (NumberFormatException e) {
185                     }
186                 }
187 
188                 recentStrikes = new FontStrike[MINSTRIKES];
189 
190                 return null;
191             }
192         });
193     }
194 
195 
refStrike(FontStrike strike)196     static void refStrike(FontStrike strike) {
197         int index = recentStrikeIndex;
198         recentStrikes[index] = strike;
199         index++;
200         if (index == MINSTRIKES) {
201             index = 0;
202         }
203         recentStrikeIndex = index;
204     }
205 
doDispose(FontStrikeDisposer disposer)206     private static void doDispose(FontStrikeDisposer disposer) {
207         if (disposer.intGlyphImages != null) {
208             freeCachedIntMemory(disposer.intGlyphImages,
209                     disposer.pScalerContext);
210         } else if (disposer.longGlyphImages != null) {
211             freeCachedLongMemory(disposer.longGlyphImages,
212                     disposer.pScalerContext);
213         } else if (disposer.segIntGlyphImages != null) {
214             /* NB Now making multiple JNI calls in this case.
215              * But assuming that there's a reasonable amount of locality
216              * rather than sparse references then it should be OK.
217              */
218             for (int i=0; i<disposer.segIntGlyphImages.length; i++) {
219                 if (disposer.segIntGlyphImages[i] != null) {
220                     freeCachedIntMemory(disposer.segIntGlyphImages[i],
221                             disposer.pScalerContext);
222                     /* native will only free the scaler context once */
223                     disposer.pScalerContext = 0L;
224                     disposer.segIntGlyphImages[i] = null;
225                 }
226             }
227             /* This may appear inefficient but it should only be invoked
228              * for a strike that never was asked to rasterise a glyph.
229              */
230             if (disposer.pScalerContext != 0L) {
231                 freeCachedIntMemory(new int[0], disposer.pScalerContext);
232             }
233         } else if (disposer.segLongGlyphImages != null) {
234             for (int i=0; i<disposer.segLongGlyphImages.length; i++) {
235                 if (disposer.segLongGlyphImages[i] != null) {
236                     freeCachedLongMemory(disposer.segLongGlyphImages[i],
237                             disposer.pScalerContext);
238                     disposer.pScalerContext = 0L;
239                     disposer.segLongGlyphImages[i] = null;
240                 }
241             }
242             if (disposer.pScalerContext != 0L) {
243                 freeCachedLongMemory(new long[0], disposer.pScalerContext);
244             }
245         } else if (disposer.pScalerContext != 0L) {
246             /* Rarely a strike may have been created that never cached
247              * any glyphs. In this case we still want to free the scaler
248              * context.
249              */
250             if (longAddresses()) {
251                 freeCachedLongMemory(new long[0], disposer.pScalerContext);
252             } else {
253                 freeCachedIntMemory(new int[0], disposer.pScalerContext);
254             }
255         }
256     }
257 
longAddresses()258     private static boolean longAddresses() {
259         return nativeAddressSize == 8;
260     }
261 
disposeStrike(final FontStrikeDisposer disposer)262     static void disposeStrike(final FontStrikeDisposer disposer) {
263         // we need to execute the strike disposal on the rendering thread
264         // because they may be accessed on that thread at the time of the
265         // disposal (for example, when the accel. cache is invalidated)
266 
267         // Whilst this is a bit heavyweight, in most applications
268         // strike disposal is a relatively infrequent operation, so it
269         // doesn't matter. But in some tests that use vast numbers
270         // of strikes, the switching back and forth is measurable.
271         // So the "pollRemove" call is added to batch up the work.
272         // If we are polling we know we've already been called back
273         // and can directly dispose the record.
274         // Also worrisome is the necessity of getting a GC here.
275 
276         if (Disposer.pollingQueue) {
277             doDispose(disposer);
278             return;
279         }
280 
281         RenderQueue rq = null;
282         GraphicsEnvironment ge =
283             GraphicsEnvironment.getLocalGraphicsEnvironment();
284         if (!GraphicsEnvironment.isHeadless()) {
285             GraphicsConfiguration gc =
286                 ge.getDefaultScreenDevice().getDefaultConfiguration();
287             if (gc instanceof AccelGraphicsConfig) {
288                 AccelGraphicsConfig agc = (AccelGraphicsConfig)gc;
289                 BufferedContext bc = agc.getContext();
290                 if (bc != null) {
291                     rq = bc.getRenderQueue();
292                 }
293             }
294         }
295         if (rq != null) {
296             rq.lock();
297             try {
298                 rq.flushAndInvokeNow(new Runnable() {
299                     public void run() {
300                         doDispose(disposer);
301                         Disposer.pollRemove();
302                     }
303                 });
304             } finally {
305                 rq.unlock();
306             }
307         } else {
308             doDispose(disposer);
309         }
310     }
311 
freeIntPointer(int ptr)312     static native void freeIntPointer(int ptr);
freeLongPointer(long ptr)313     static native void freeLongPointer(long ptr);
freeIntMemory(int[] glyphPtrs, long pContext)314     private static native void freeIntMemory(int[] glyphPtrs, long pContext);
freeLongMemory(long[] glyphPtrs, long pContext)315     private static native void freeLongMemory(long[] glyphPtrs, long pContext);
316 
freeCachedIntMemory(int[] glyphPtrs, long pContext)317     private static void freeCachedIntMemory(int[] glyphPtrs, long pContext) {
318         synchronized(disposeListeners) {
319             if (disposeListeners.size() > 0) {
320                 ArrayList<Long> gids = null;
321 
322                 for (int i = 0; i < glyphPtrs.length; i++) {
323                     if (glyphPtrs[i] != 0 && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
324 
325                         if (gids == null) {
326                             gids = new ArrayList<Long>();
327                         }
328                         gids.add((long) glyphPtrs[i]);
329                     }
330                 }
331 
332                 if (gids != null) {
333                     // Any reference by the disposers to the native glyph ptrs
334                     // must be done before this returns.
335                     notifyDisposeListeners(gids);
336                 }
337             }
338         }
339 
340         freeIntMemory(glyphPtrs, pContext);
341     }
342 
freeCachedLongMemory(long[] glyphPtrs, long pContext)343     private static void  freeCachedLongMemory(long[] glyphPtrs, long pContext) {
344         synchronized(disposeListeners) {
345         if (disposeListeners.size() > 0)  {
346                 ArrayList<Long> gids = null;
347 
348                 for (int i=0; i < glyphPtrs.length; i++) {
349                     if (glyphPtrs[i] != 0
350                             && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
351 
352                         if (gids == null) {
353                             gids = new ArrayList<Long>();
354                         }
355                         gids.add(glyphPtrs[i]);
356                     }
357                 }
358 
359                 if (gids != null) {
360                     // Any reference by the disposers to the native glyph ptrs
361                     // must be done before this returns.
362                     notifyDisposeListeners(gids);
363                 }
364         }
365         }
366 
367         freeLongMemory(glyphPtrs, pContext);
368     }
369 
addGlyphDisposedListener(GlyphDisposedListener listener)370     public static void addGlyphDisposedListener(GlyphDisposedListener listener) {
371         synchronized(disposeListeners) {
372             disposeListeners.add(listener);
373         }
374     }
375 
notifyDisposeListeners(ArrayList<Long> glyphs)376     private static void notifyDisposeListeners(ArrayList<Long> glyphs) {
377         for (GlyphDisposedListener listener : disposeListeners) {
378             listener.glyphDisposed(glyphs);
379         }
380     }
381 
getStrikeRef(FontStrike strike)382     public static Reference<FontStrike> getStrikeRef(FontStrike strike) {
383         return getStrikeRef(strike, cacheRefTypeWeak);
384     }
385 
getStrikeRef(FontStrike strike, boolean weak)386     public static Reference<FontStrike> getStrikeRef(FontStrike strike, boolean weak) {
387         /* Some strikes may have no disposer as there's nothing
388          * for them to free, as they allocated no native resource
389          * eg, if they did not allocate resources because of a problem,
390          * or they never hold native resources. So they create no disposer.
391          * But any strike that reaches here that has a null disposer is
392          * a potential memory leak.
393          */
394         if (strike.disposer == null) {
395             if (weak) {
396                 return new WeakReference<>(strike);
397             } else {
398                 return new SoftReference<>(strike);
399             }
400         }
401 
402         if (weak) {
403             return new WeakDisposerRef(strike);
404         } else {
405             return new SoftDisposerRef(strike);
406         }
407     }
408 
409     static interface DisposableStrike {
getDisposer()410         FontStrikeDisposer getDisposer();
411     }
412 
413     static class SoftDisposerRef
414         extends SoftReference<FontStrike> implements DisposableStrike {
415 
416         private FontStrikeDisposer disposer;
417 
getDisposer()418         public FontStrikeDisposer getDisposer() {
419             return disposer;
420         }
421 
422         @SuppressWarnings("unchecked")
SoftDisposerRef(FontStrike strike)423         SoftDisposerRef(FontStrike strike) {
424             super(strike, StrikeCache.refQueue);
425             disposer = strike.disposer;
426             Disposer.addReference((Reference<Object>)(Reference)this, disposer);
427         }
428     }
429 
430     static class WeakDisposerRef
431         extends WeakReference<FontStrike> implements DisposableStrike {
432 
433         private FontStrikeDisposer disposer;
434 
getDisposer()435         public FontStrikeDisposer getDisposer() {
436             return disposer;
437         }
438 
439         @SuppressWarnings("unchecked")
WeakDisposerRef(FontStrike strike)440         WeakDisposerRef(FontStrike strike) {
441             super(strike, StrikeCache.refQueue);
442             disposer = strike.disposer;
443             Disposer.addReference((Reference<Object>)(Reference)this, disposer);
444         }
445     }
446 
447 }
448