StrikeCache.java revision 3611
0N/A/*
4185N/A * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
1472N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1472N/A *
1472N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
0N/A * or visit www.oracle.com if you need additional information or have any
0N/A * questions.
0N/A */
1879N/A
1879N/Apackage sun.font;
1879N/A
0N/Aimport java.awt.GraphicsConfiguration;
0N/Aimport java.awt.GraphicsEnvironment;
0N/Aimport java.lang.ref.Reference;
0N/Aimport java.lang.ref.ReferenceQueue;
0N/Aimport java.lang.ref.SoftReference;
0N/Aimport java.lang.ref.WeakReference;
0N/Aimport java.util.*;
0N/A
0N/Aimport sun.java2d.Disposer;
0N/Aimport sun.java2d.pipe.BufferedContext;
0N/Aimport sun.java2d.pipe.RenderQueue;
0N/Aimport sun.java2d.pipe.hw.AccelGraphicsConfig;
0N/Aimport sun.misc.Unsafe;
0N/A
0N/A/**
0N/A
0N/AA FontStrike is the keeper of scaled glyph image data which is expensive
0N/Ato compute so needs to be cached.
0N/ASo long as that data may be being used it cannot be invalidated.
0N/AYet we also need to limit the amount of native memory and number of
0N/Astrike objects in use.
0N/AFor scaleability and ease of use, a key goal is multi-threaded read
0N/Aaccess to a strike, so that it may be shared by multiple client objects,
0N/Apotentially executing on different threads, with no special reference
0N/Acounting or "check-out/check-in" requirements which would pass on the
0N/Aburden of keeping track of strike references to the SG2D and other clients.
0N/A
610N/AA cache of strikes is maintained via Reference objects.
610N/AThis helps in two ways :
4185N/A1. The VM will free references when memory is low or they have not been
0N/Aused in a long time.
0N/A2. Reference queues provide a way to get notification of this so we can
0N/Afree native memory resources.
0N/A
0N/A */
0N/A
0N/Apublic final class StrikeCache {
0N/A
0N/A static final Unsafe unsafe = Unsafe.getUnsafe();
0N/A
0N/A static ReferenceQueue refQueue = Disposer.getQueue();
0N/A
48N/A static ArrayList<GlyphDisposedListener> disposeListeners = new ArrayList<GlyphDisposedListener>(1);
0N/A
0N/A
0N/A /* Reference objects may have their referents cleared when GC chooses.
0N/A * During application client start-up there is typically at least one
0N/A * GC which causes the hotspot VM to clear soft (not just weak) references
0N/A * Thus not only is there a GC pause, but the work done do rasterise
0N/A * glyphs that are fairly certain to be needed again almost immediately
0N/A * is thrown away. So for performance reasons a simple optimisation is to
0N/A * keep up to 8 strong references to strikes to reduce the chance of
0N/A * GC'ing strikes that have been used recently. Note that this may not
0N/A * suffice in Solaris UTF-8 locales where a single composite strike may be
0N/A * composed of 15 individual strikes, plus the composite strike.
0N/A * And this assumes the new architecture doesn't maintain strikes for
0N/A * natively accessed bitmaps. It may be worth "tuning" the number of
4185N/A * strikes kept around for the platform or locale.
4185N/A * Since no attempt is made to ensure uniqueness or ensure synchronized
4185N/A * access there is no guarantee that this cache will ensure that unique
4185N/A * strikes are cached. Every time a strike is looked up it is added
4185N/A * to the current index in this cache. All this cache has to do to be
4185N/A * worthwhile is prevent excessive cache flushing of strikes that are
4185N/A * referenced frequently. The logic that adds references here could be
0N/A * tweaked to keep only strikes that represent untransformed, screen
0N/A * sizes as that's the typical performance case.
0N/A */
0N/A static int MINSTRIKES = 8; // can be overridden by property
0N/A static int recentStrikeIndex = 0;
0N/A static FontStrike[] recentStrikes;
0N/A static boolean cacheRefTypeWeak;
0N/A
0N/A /*
0N/A * Native sizes and offsets for glyph cache
0N/A * There are 10 values.
0N/A */
0N/A static int nativeAddressSize;
0N/A static int glyphInfoSize;
0N/A static int xAdvanceOffset;
0N/A static int yAdvanceOffset;
0N/A static int boundsOffset;
0N/A static int widthOffset;
0N/A static int heightOffset;
0N/A static int rowBytesOffset;
0N/A static int topLeftXOffset;
0N/A static int topLeftYOffset;
0N/A static int pixelDataOffset;
0N/A static int cacheCellOffset;
0N/A static int managedOffset;
0N/A static long invisibleGlyphPtr;
0N/A
0N/A /* Native method used to return information used for unsafe
0N/A * access to native data.
0N/A * return values as follows:-
0N/A * arr[0] = size of an address/pointer.
0N/A * arr[1] = size of a GlyphInfo
0N/A * arr[2] = offset of advanceX
0N/A * arr[3] = offset of advanceY
0N/A * arr[4] = offset of width
0N/A * arr[5] = offset of height
0N/A * arr[6] = offset of rowBytes
0N/A * arr[7] = offset of topLeftX
0N/A * arr[8] = offset of topLeftY
0N/A * arr[9] = offset of pixel data.
0N/A * arr[10] = address of a GlyphImageRef representing the invisible glyph
0N/A */
1879N/A static native void getGlyphCacheDescription(long[] infoArray);
1879N/A
static {
long[] nativeInfo = new long[13];
getGlyphCacheDescription(nativeInfo);
//Can also get address size from Unsafe class :-
//nativeAddressSize = unsafe.addressSize();
nativeAddressSize = (int)nativeInfo[0];
glyphInfoSize = (int)nativeInfo[1];
xAdvanceOffset = (int)nativeInfo[2];
yAdvanceOffset = (int)nativeInfo[3];
widthOffset = (int)nativeInfo[4];
heightOffset = (int)nativeInfo[5];
rowBytesOffset = (int)nativeInfo[6];
topLeftXOffset = (int)nativeInfo[7];
topLeftYOffset = (int)nativeInfo[8];
pixelDataOffset = (int)nativeInfo[9];
invisibleGlyphPtr = nativeInfo[10];
cacheCellOffset = (int) nativeInfo[11];
managedOffset = (int) nativeInfo[12];
if (nativeAddressSize < 4) {
throw new InternalError("Unexpected address size for font data: " +
nativeAddressSize);
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
/* Allow a client to override the reference type used to
* cache strikes. The default is "soft" which hints to keep
* the strikes around. This property allows the client to
* override this to "weak" which hint to the GC to free
* memory more agressively.
*/
String refType =
System.getProperty("sun.java2d.font.reftype", "soft");
cacheRefTypeWeak = refType.equals("weak");
String minStrikesStr =
System.getProperty("sun.java2d.font.minstrikes");
if (minStrikesStr != null) {
try {
MINSTRIKES = Integer.parseInt(minStrikesStr);
if (MINSTRIKES <= 0) {
MINSTRIKES = 1;
}
} catch (NumberFormatException e) {
}
}
recentStrikes = new FontStrike[MINSTRIKES];
return null;
}
});
}
static void refStrike(FontStrike strike) {
int index = recentStrikeIndex;
recentStrikes[index] = strike;
index++;
if (index == MINSTRIKES) {
index = 0;
}
recentStrikeIndex = index;
}
private static final void doDispose(FontStrikeDisposer disposer) {
if (disposer.intGlyphImages != null) {
freeCachedIntMemory(disposer.intGlyphImages,
disposer.pScalerContext);
} else if (disposer.longGlyphImages != null) {
freeCachedLongMemory(disposer.longGlyphImages,
disposer.pScalerContext);
} else if (disposer.segIntGlyphImages != null) {
/* NB Now making multiple JNI calls in this case.
* But assuming that there's a reasonable amount of locality
* rather than sparse references then it should be OK.
*/
for (int i=0; i<disposer.segIntGlyphImages.length; i++) {
if (disposer.segIntGlyphImages[i] != null) {
freeCachedIntMemory(disposer.segIntGlyphImages[i],
disposer.pScalerContext);
/* native will only free the scaler context once */
disposer.pScalerContext = 0L;
disposer.segIntGlyphImages[i] = null;
}
}
/* This may appear inefficient but it should only be invoked
* for a strike that never was asked to rasterise a glyph.
*/
if (disposer.pScalerContext != 0L) {
freeCachedIntMemory(new int[0], disposer.pScalerContext);
}
} else if (disposer.segLongGlyphImages != null) {
for (int i=0; i<disposer.segLongGlyphImages.length; i++) {
if (disposer.segLongGlyphImages[i] != null) {
freeCachedLongMemory(disposer.segLongGlyphImages[i],
disposer.pScalerContext);
disposer.pScalerContext = 0L;
disposer.segLongGlyphImages[i] = null;
}
}
if (disposer.pScalerContext != 0L) {
freeCachedLongMemory(new long[0], disposer.pScalerContext);
}
} else if (disposer.pScalerContext != 0L) {
/* Rarely a strike may have been created that never cached
* any glyphs. In this case we still want to free the scaler
* context.
*/
if (longAddresses()) {
freeCachedLongMemory(new long[0], disposer.pScalerContext);
} else {
freeCachedIntMemory(new int[0], disposer.pScalerContext);
}
}
}
private static boolean longAddresses() {
return nativeAddressSize == 8;
}
static void disposeStrike(final FontStrikeDisposer disposer) {
// we need to execute the strike disposal on the rendering thread
// because they may be accessed on that thread at the time of the
// disposal (for example, when the accel. cache is invalidated)
// Whilst this is a bit heavyweight, in most applications
// strike disposal is a relatively infrequent operation, so it
// doesn't matter. But in some tests that use vast numbers
// of strikes, the switching back and forth is measurable.
// So the "pollRemove" call is added to batch up the work.
// If we are polling we know we've already been called back
// and can directly dispose the record.
// Also worrisome is the necessity of getting a GC here.
if (Disposer.pollingQueue) {
doDispose(disposer);
return;
}
RenderQueue rq = null;
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
if (!ge.isHeadless()) {
GraphicsConfiguration gc =
ge.getDefaultScreenDevice().getDefaultConfiguration();
if (gc instanceof AccelGraphicsConfig) {
AccelGraphicsConfig agc = (AccelGraphicsConfig)gc;
BufferedContext bc = agc.getContext();
if (bc != null) {
rq = bc.getRenderQueue();
}
}
}
if (rq != null) {
rq.lock();
try {
rq.flushAndInvokeNow(new Runnable() {
public void run() {
doDispose(disposer);
Disposer.pollRemove();
}
});
} finally {
rq.unlock();
}
} else {
doDispose(disposer);
}
}
static native void freeIntPointer(int ptr);
static native void freeLongPointer(long ptr);
private static native void freeIntMemory(int[] glyphPtrs, long pContext);
private static native void freeLongMemory(long[] glyphPtrs, long pContext);
private static void freeCachedIntMemory(int[] glyphPtrs, long pContext) {
synchronized(disposeListeners) {
if (disposeListeners.size() > 0) {
ArrayList<Long> gids = null;
for (int i = 0; i < glyphPtrs.length; i++) {
if (glyphPtrs[i] != 0 && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
if (gids == null) {
gids = new ArrayList<Long>();
}
gids.add((long) glyphPtrs[i]);
}
}
if (gids != null) {
// Any reference by the disposers to the native glyph ptrs
// must be done before this returns.
notifyDisposeListeners(gids);
}
}
}
freeIntMemory(glyphPtrs, pContext);
}
private static void freeCachedLongMemory(long[] glyphPtrs, long pContext) {
synchronized(disposeListeners) {
if (disposeListeners.size() > 0) {
ArrayList<Long> gids = null;
for (int i=0; i < glyphPtrs.length; i++) {
if (glyphPtrs[i] != 0
&& unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
if (gids == null) {
gids = new ArrayList<Long>();
}
gids.add((long) glyphPtrs[i]);
}
}
if (gids != null) {
// Any reference by the disposers to the native glyph ptrs
// must be done before this returns.
notifyDisposeListeners(gids);
}
}
}
freeLongMemory(glyphPtrs, pContext);
}
public static void addGlyphDisposedListener(GlyphDisposedListener listener) {
synchronized(disposeListeners) {
disposeListeners.add(listener);
}
}
private static void notifyDisposeListeners(ArrayList<Long> glyphs) {
for (GlyphDisposedListener listener : disposeListeners) {
listener.glyphDisposed(glyphs);
}
}
public static Reference getStrikeRef(FontStrike strike) {
return getStrikeRef(strike, cacheRefTypeWeak);
}
public static Reference getStrikeRef(FontStrike strike, boolean weak) {
/* Some strikes may have no disposer as there's nothing
* for them to free, as they allocated no native resource
* eg, if they did not allocate resources because of a problem,
* or they never hold native resources. So they create no disposer.
* But any strike that reaches here that has a null disposer is
* a potential memory leak.
*/
if (strike.disposer == null) {
if (weak) {
return new WeakReference(strike);
} else {
return new SoftReference(strike);
}
}
if (weak) {
return new WeakDisposerRef(strike);
} else {
return new SoftDisposerRef(strike);
}
}
static interface DisposableStrike {
FontStrikeDisposer getDisposer();
}
static class SoftDisposerRef
extends SoftReference implements DisposableStrike {
private FontStrikeDisposer disposer;
public FontStrikeDisposer getDisposer() {
return disposer;
}
SoftDisposerRef(FontStrike strike) {
super(strike, StrikeCache.refQueue);
disposer = strike.disposer;
Disposer.addReference(this, disposer);
}
}
static class WeakDisposerRef
extends WeakReference implements DisposableStrike {
private FontStrikeDisposer disposer;
public FontStrikeDisposer getDisposer() {
return disposer;
}
WeakDisposerRef(FontStrike strike) {
super(strike, StrikeCache.refQueue);
disposer = strike.disposer;
Disposer.addReference(this, disposer);
}
}
}