mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-24 18:03:10 +00:00
548 lines
21 KiB
Java
548 lines
21 KiB
Java
/*
|
|
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.font;
|
|
|
|
import java.awt.GraphicsConfiguration;
|
|
import java.awt.GraphicsEnvironment;
|
|
|
|
import java.lang.foreign.MemoryLayout;
|
|
import java.lang.foreign.MemorySegment;
|
|
import static java.lang.foreign.MemorySegment.NULL;
|
|
import java.lang.foreign.StructLayout;
|
|
import java.lang.foreign.ValueLayout;
|
|
import static java.lang.foreign.ValueLayout.*;
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.lang.invoke.VarHandle;
|
|
|
|
import java.lang.ref.Reference;
|
|
import java.lang.ref.ReferenceQueue;
|
|
import java.lang.ref.SoftReference;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.*;
|
|
|
|
import sun.java2d.Disposer;
|
|
import sun.java2d.pipe.BufferedContext;
|
|
import sun.java2d.pipe.RenderQueue;
|
|
import sun.java2d.pipe.hw.AccelGraphicsConfig;
|
|
|
|
/**
|
|
|
|
A FontStrike is the keeper of scaled glyph image data which is expensive
|
|
to compute so needs to be cached.
|
|
So long as that data may be being used it cannot be invalidated.
|
|
Yet we also need to limit the amount of native memory and number of
|
|
strike objects in use.
|
|
For scalability and ease of use, a key goal is multi-threaded read
|
|
access to a strike, so that it may be shared by multiple client objects,
|
|
potentially executing on different threads, with no special reference
|
|
counting or "check-out/check-in" requirements which would pass on the
|
|
burden of keeping track of strike references to the SG2D and other clients.
|
|
|
|
A cache of strikes is maintained via Reference objects.
|
|
This helps in two ways :
|
|
1. The VM will free references when memory is low or they have not been
|
|
used in a long time.
|
|
2. Reference queues provide a way to get notification of this so we can
|
|
free native memory resources.
|
|
|
|
*/
|
|
|
|
public final class StrikeCache {
|
|
|
|
static ReferenceQueue<Object> refQueue = Disposer.getQueue();
|
|
|
|
static ArrayList<GlyphDisposedListener> disposeListeners = new ArrayList<GlyphDisposedListener>(1);
|
|
|
|
|
|
/* Reference objects may have their referents cleared when GC chooses.
|
|
* During application client start-up there is typically at least one
|
|
* GC which causes the hotspot VM to clear soft (not just weak) references
|
|
* Thus not only is there a GC pause, but the work done do rasterise
|
|
* glyphs that are fairly certain to be needed again almost immediately
|
|
* is thrown away. So for performance reasons a simple optimisation is to
|
|
* keep up to 8 strong references to strikes to reduce the chance of
|
|
* GC'ing strikes that have been used recently. Note that this may not
|
|
* suffice in Solaris UTF-8 locales where a single composite strike may be
|
|
* composed of 15 individual strikes, plus the composite strike.
|
|
* And this assumes the new architecture doesn't maintain strikes for
|
|
* natively accessed bitmaps. It may be worth "tuning" the number of
|
|
* strikes kept around for the platform or locale.
|
|
* Since no attempt is made to ensure uniqueness or ensure synchronized
|
|
* access there is no guarantee that this cache will ensure that unique
|
|
* strikes are cached. Every time a strike is looked up it is added
|
|
* to the current index in this cache. All this cache has to do to be
|
|
* worthwhile is prevent excessive cache flushing of strikes that are
|
|
* referenced frequently. The logic that adds references here could be
|
|
* tweaked to keep only strikes that represent untransformed, screen
|
|
* sizes as that's the typical performance case.
|
|
*/
|
|
static int MINSTRIKES = 8; // can be overridden by property
|
|
static int recentStrikeIndex = 0;
|
|
static FontStrike[] recentStrikes;
|
|
static boolean cacheRefTypeWeak;
|
|
|
|
/*
|
|
* Native sizes and accessors for glyph cache structure.
|
|
* There are 10 values. Also need native address size and a long which
|
|
* references a memory address for a "null" glyph image.
|
|
*/
|
|
static final int nativeAddressSize = (int)ValueLayout.ADDRESS.byteSize();
|
|
static final long invisibleGlyphPtr = getInvisibleGlyphPtr(); // a singleton.
|
|
|
|
static native long getInvisibleGlyphPtr();
|
|
|
|
public static final StructLayout GlyphImageLayout = MemoryLayout.structLayout(
|
|
JAVA_FLOAT.withName("xAdvance"), // 0+4=4,
|
|
JAVA_FLOAT.withName("yAdvance"), // 4+4=8,
|
|
JAVA_CHAR.withName("width"), // 8+2=10,
|
|
JAVA_CHAR.withName("height"), // 10+2=12
|
|
JAVA_CHAR.withName("rowBytes"), // 12+2=14
|
|
JAVA_BYTE.withName("managed"), // 14+1=15
|
|
MemoryLayout.paddingLayout(1), // 15+1=16
|
|
JAVA_FLOAT.withName("topLeftX"), // 16+4=20
|
|
JAVA_FLOAT.withName("topLeftY"), // 20+4=24
|
|
ADDRESS.withName("cellInfo"), // 24+8=32
|
|
ADDRESS.withName("image") // 32+8=40
|
|
);
|
|
|
|
private static final long GLYPHIMAGESIZE = GlyphImageLayout.byteSize();
|
|
|
|
private static VarHandle getVarHandle(StructLayout struct, String name) {
|
|
VarHandle h = struct.varHandle(PathElement.groupElement(name));
|
|
/* insert 0 offset so don't need to pass arg every time */
|
|
return MethodHandles.insertCoordinates(h, 1, 0L).withInvokeExactBehavior();
|
|
}
|
|
|
|
private static final VarHandle xAdvanceHandle = getVarHandle(GlyphImageLayout, "xAdvance");
|
|
private static final VarHandle yAdvanceHandle = getVarHandle(GlyphImageLayout, "yAdvance");
|
|
private static final VarHandle widthHandle = getVarHandle(GlyphImageLayout, "width");
|
|
private static final VarHandle heightHandle = getVarHandle(GlyphImageLayout, "height");
|
|
private static final VarHandle rowBytesHandle = getVarHandle(GlyphImageLayout, "rowBytes");
|
|
private static final VarHandle managedHandle = getVarHandle(GlyphImageLayout, "managed");
|
|
private static final VarHandle topLeftXHandle = getVarHandle(GlyphImageLayout, "topLeftX");
|
|
private static final VarHandle topLeftYHandle = getVarHandle(GlyphImageLayout, "topLeftY");
|
|
private static final VarHandle cellInfoHandle = getVarHandle(GlyphImageLayout, "cellInfo");
|
|
private static final VarHandle imageHandle = getVarHandle(GlyphImageLayout, "image");
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final float getGlyphXAdvance(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (float)xAdvanceHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final void setGlyphXAdvance(long ptr, float val) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
xAdvanceHandle.set(seg, val);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final float getGlyphYAdvance(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (float)yAdvanceHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final char getGlyphWidth(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (char)widthHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final char getGlyphHeight(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (char)heightHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final char getGlyphRowBytes(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (char)rowBytesHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final byte getGlyphManaged(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (byte)managedHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final float getGlyphTopLeftX(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (float)topLeftXHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final float getGlyphTopLeftY(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return (float)topLeftYHandle.get(seg);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final long getGlyphCellInfo(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return ((MemorySegment)cellInfoHandle.get(seg)).address();
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final void setGlyphCellInfo(long ptr, long val) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
MemorySegment segval = MemorySegment.ofAddress(val);
|
|
cellInfoHandle.set(seg, segval);
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final long getGlyphImagePtr(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
return ((MemorySegment)imageHandle.get(seg)).address();
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final MemorySegment getGlyphPixelData(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
char hgt = (char)heightHandle.get(seg);
|
|
char rb = (char)rowBytesHandle.get(seg);
|
|
MemorySegment pixelData = (MemorySegment)imageHandle.get(seg);
|
|
pixelData = pixelData.reinterpret(rb * hgt);
|
|
return pixelData;
|
|
}
|
|
|
|
@SuppressWarnings("restricted")
|
|
static final byte[] getGlyphPixelBytes(long ptr) {
|
|
MemorySegment seg = MemorySegment.ofAddress(ptr);
|
|
seg = seg.reinterpret(GLYPHIMAGESIZE);
|
|
char hgt = (char)heightHandle.get(seg);
|
|
char rb = (char)rowBytesHandle.get(seg);
|
|
MemorySegment pixelData = (MemorySegment)imageHandle.get(seg);
|
|
int sz = rb * hgt;
|
|
pixelData = pixelData.reinterpret(sz);
|
|
return pixelData.toArray(ValueLayout.JAVA_BYTE);
|
|
}
|
|
|
|
static final byte getPixelByte(MemorySegment pixelData, long index) {
|
|
return pixelData.getAtIndex(JAVA_BYTE, index);
|
|
}
|
|
|
|
static {
|
|
initStatic();
|
|
}
|
|
|
|
private static void initStatic() {
|
|
|
|
if (nativeAddressSize < 4) {
|
|
throw new InternalError("Unexpected address size for font data: " +
|
|
nativeAddressSize);
|
|
}
|
|
|
|
/* 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 aggressively.
|
|
*/
|
|
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];
|
|
}
|
|
|
|
|
|
static void refStrike(FontStrike strike) {
|
|
int index = recentStrikeIndex;
|
|
recentStrikes[index] = strike;
|
|
index++;
|
|
if (index == MINSTRIKES) {
|
|
index = 0;
|
|
}
|
|
recentStrikeIndex = index;
|
|
}
|
|
|
|
private static 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 (!GraphicsEnvironment.isHeadless()) {
|
|
GraphicsConfiguration gc =
|
|
ge.getDefaultScreenDevice().getDefaultConfiguration();
|
|
if (gc instanceof AccelGraphicsConfig agc) {
|
|
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) && getGlyphManaged(glyphPtrs[i]) == 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) && getGlyphManaged(glyphPtrs[i]) == 0) {
|
|
|
|
if (gids == null) {
|
|
gids = new ArrayList<Long>();
|
|
}
|
|
gids.add(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<FontStrike> getStrikeRef(FontStrike strike) {
|
|
return getStrikeRef(strike, cacheRefTypeWeak);
|
|
}
|
|
|
|
public static Reference<FontStrike> 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<FontStrike> implements DisposableStrike {
|
|
|
|
private FontStrikeDisposer disposer;
|
|
|
|
public FontStrikeDisposer getDisposer() {
|
|
return disposer;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
SoftDisposerRef(FontStrike strike) {
|
|
super(strike, StrikeCache.refQueue);
|
|
disposer = strike.disposer;
|
|
Disposer.addReference((Reference<Object>)(Reference)this, disposer);
|
|
}
|
|
}
|
|
|
|
static class WeakDisposerRef
|
|
extends WeakReference<FontStrike> implements DisposableStrike {
|
|
|
|
private FontStrikeDisposer disposer;
|
|
|
|
public FontStrikeDisposer getDisposer() {
|
|
return disposer;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
WeakDisposerRef(FontStrike strike) {
|
|
super(strike, StrikeCache.refQueue);
|
|
disposer = strike.disposer;
|
|
Disposer.addReference((Reference<Object>)(Reference)this, disposer);
|
|
}
|
|
}
|
|
|
|
}
|