| /* | |
| * Copyright (c) 2003, 2024, 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) { | |
| 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) && 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); | |
| } | |
| } | |
| } |