diff --git a/src/java.desktop/share/classes/sun/font/StrikeCache.java b/src/java.desktop/share/classes/sun/font/StrikeCache.java index 80099728b07f..4cff814e33cb 100644 --- a/src/java.desktop/share/classes/sun/font/StrikeCache.java +++ b/src/java.desktop/share/classes/sun/font/StrikeCache.java @@ -115,17 +115,20 @@ public final class StrikeCache { 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 + 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 + JAVA_BYTE.withName("subpixelResolutionX"), // 15+1=16 + JAVA_BYTE.withName("subpixelResolutionY"), // 16+1=17 + MemoryLayout.paddingLayout(3), // 17+3=20 + JAVA_FLOAT.withName("topLeftX"), // 20+4=24 + JAVA_FLOAT.withName("topLeftY"), // 24+4=28 + MemoryLayout.paddingLayout(4), // 28+4=32 + ADDRESS.withName("cellInfo"), // 32+8=40 + ADDRESS.withName("image") // 40+8=48 ); private static final long GLYPHIMAGESIZE = GlyphImageLayout.byteSize(); @@ -136,16 +139,18 @@ public final class StrikeCache { 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"); + 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 subpixelResolutionXHandle = getVarHandle(GlyphImageLayout, "subpixelResolutionX"); + private static final VarHandle subpixelResolutionYHandle = getVarHandle(GlyphImageLayout, "subpixelResolutionY"); + 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) { @@ -203,6 +208,20 @@ public final class StrikeCache { return (byte)managedHandle.get(seg); } + @SuppressWarnings("restricted") + static final byte getGlyphSubpixelResolutionX(long ptr) { + MemorySegment seg = MemorySegment.ofAddress(ptr); + seg = seg.reinterpret(GLYPHIMAGESIZE); + return (byte)subpixelResolutionXHandle.get(seg); + } + + @SuppressWarnings("restricted") + static final byte getGlyphSubpixelResolutionY(long ptr) { + MemorySegment seg = MemorySegment.ofAddress(ptr); + seg = seg.reinterpret(GLYPHIMAGESIZE); + return (byte)subpixelResolutionYHandle.get(seg); + } + @SuppressWarnings("restricted") static final float getGlyphTopLeftX(long ptr) { MemorySegment seg = MemorySegment.ofAddress(ptr); diff --git a/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java b/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java index 9ca8edb68b00..842b69b12522 100644 --- a/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java +++ b/src/java.desktop/unix/classes/sun/font/XRGlyphCache.java @@ -48,7 +48,6 @@ public final class XRGlyphCache implements GlyphDisposedListener { XRCompositeManager maskBuffer; HashMap cacheMap = new HashMap(256); - int nextID = 1; MutableInteger tmp = new MutableInteger(0); int grayGlyphSet; @@ -59,7 +58,7 @@ public final class XRGlyphCache implements GlyphDisposedListener { int cachedPixels = 0; static final int MAX_CACHED_PIXELS = 100000; - ArrayList freeGlyphIDs = new ArrayList(255); + final GlyphIDAllocator glyphIDAllocator = new GlyphIDAllocator(); static final boolean batchGlyphUpload = true; // Boolean.parseBoolean(System.getProperty("sun.java2d.xrender.batchGlyphUpload")); @@ -98,14 +97,6 @@ public final class XRGlyphCache implements GlyphDisposedListener { } } - protected int getFreeGlyphID() { - if (freeGlyphIDs.size() > 0) { - int newID = freeGlyphIDs.remove(freeGlyphIDs.size() - 1); - return newID; - } - return nextID++; - } - protected XRGlyphCacheEntry getEntryForPointer(long imgPtr) { int id = XRGlyphCacheEntry.getGlyphID(imgPtr); @@ -134,7 +125,9 @@ public final class XRGlyphCache implements GlyphDisposedListener { // Find uncached glyphs and queue them for upload if ((glyph = getEntryForPointer(imgPtrs[i])) == null) { glyph = new XRGlyphCacheEntry(imgPtrs[i], glyphList); - glyph.setGlyphID(getFreeGlyphID()); + glyph.setGlyphID(glyphIDAllocator.allocateID( + glyph.getSubpixelResolutionX() * + glyph.getSubpixelResolutionY())); cacheMap.put(new MutableInteger(glyph.getGlyphID()), glyph); if (uncachedGlyphs == null) { @@ -282,15 +275,18 @@ public final class XRGlyphCache implements GlyphDisposedListener { for (int i=0; i < glyphIdList.getSize(); i++) { int glyphId = glyphIdList.getInt(i); - freeGlyphIDs.add(glyphId); tmp.setValue(glyphId); XRGlyphCacheEntry entry = cacheMap.get(tmp); + int subglyphs = entry.getSubpixelResolutionX() * entry.getSubpixelResolutionY(); + glyphIDAllocator.freeID(glyphId, subglyphs); cachedPixels -= entry.getPixelCnt(); cacheMap.remove(tmp); if (entry.getGlyphSet() == grayGlyphSet) { - removedGrayscaleGlyphs.addInt(glyphId); + for (int j = 0; j < subglyphs; j++) { + removedGrayscaleGlyphs.addInt(glyphId + j); + } } else if (entry.getGlyphSet() == lcdGlyphSet) { removedLCDGlyphs.addInt(glyphId); } else if (entry.getGlyphSet() == BGRA_GLYPH_SET) { @@ -322,4 +318,47 @@ public final class XRGlyphCache implements GlyphDisposedListener { removedBGRAGlyphPtrsCount); } } + + /** + * Each XR glyph image needs its own ID, which are allocated using + * this class. When using supplementary subpixel glyphs + * ({@code -Djava2d.font.subpixelResolution=4x2}), its XRGlyphCacheEntry + * may correspond to few images and therefore may need a range of IDs + * instead of a single one. + * @implNote This allocator uses a simple strategy for reusing IDs with + * a single pool per capacity (e.g. one pool containing only individual + * IDs and second pool containing ranges each with 8 sequential IDs). + * When pool is empty, new range of IDs is allocated by incrementing + * {@code nextID} counter. + */ + private static class GlyphIDAllocator { + + @SuppressWarnings({"unchecked", "rawtypes"}) + private List[] freeIDsByCapacity = new List[]{ + new ArrayList<>(255) + }; + private int nextID = 1; + + private int allocateID(int count) { + if (count <= freeIDsByCapacity.length) { + List pool = freeIDsByCapacity[count - 1]; + if (pool != null && !pool.isEmpty()) { + return pool.remove(pool.size() - 1); + } + } + int id = nextID; + nextID += count; + return id; + } + + private void freeID(int id, int count) { + if (count > freeIDsByCapacity.length) { + freeIDsByCapacity = Arrays.copyOf(freeIDsByCapacity, count); + } + if (freeIDsByCapacity[count - 1] == null) { + freeIDsByCapacity[count - 1] = new ArrayList<>(255); + } + freeIDsByCapacity[count - 1].add(id); + } + } } diff --git a/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java b/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java index 89045cbaa5e4..fdfca9477d73 100644 --- a/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java +++ b/src/java.desktop/unix/classes/sun/font/XRGlyphCacheEntry.java @@ -139,15 +139,19 @@ public final class XRGlyphCacheEntry { byte[] pixelBytes = StrikeCache.getGlyphPixelBytes(glyphInfoPtr); if (getType() == Type.GRAYSCALE) { - for (int line = 0; line < height; line++) { - for(int x = 0; x < paddedWidth; x++) { - if(x < width) { - os.write(pixelBytes[(line * rowBytes + x)]); - }else { - /*pad to multiple of 4 bytes per line*/ - os.write(0); + int subglyphs = getSubpixelResolutionX() * getSubpixelResolutionY(); + for (int subglyph = 0; subglyph < subglyphs; subglyph++) { + for (int line = 0; line < height; line++) { + for(int x = 0; x < paddedWidth; x++) { + if(x < width) { + os.write(pixelBytes[(line * rowBytes + x)]); + }else { + /*pad to multiple of 4 bytes per line*/ + os.write(0); + } } } + pixelDataAddress += height * rowBytes; } } else { for (int line = 0; line < height; line++) { @@ -173,6 +177,16 @@ public final class XRGlyphCacheEntry { return StrikeCache.getGlyphTopLeftY(glyphInfoPtr); } + public byte getSubpixelResolutionX() { + byte rx = StrikeCache.getGlyphSubpixelResolutionX(glyphInfoPtr); + return rx < 1 ? 1 : rx; + } + + public byte getSubpixelResolutionY() { + byte ry = StrikeCache.getGlyphSubpixelResolutionY(glyphInfoPtr); + return ry < 1 ? 1 : ry; + } + public long getGlyphInfoPtr() { return glyphInfoPtr; } @@ -217,7 +231,7 @@ public final class XRGlyphCacheEntry { } public int getPixelCnt() { - return getWidth() * getHeight(); + return getWidth() * getHeight() * getSubpixelResolutionX() * getSubpixelResolutionY(); } public boolean isPinned() { diff --git a/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java b/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java index 4b332b603b10..3be28e61bbde 100644 --- a/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java +++ b/src/java.desktop/unix/classes/sun/font/XRTextRenderer.java @@ -102,6 +102,10 @@ public final class XRTextRenderer extends GlyphListPipe { int glyphSet = cacheEntry.getGlyphSet(); + int subpixelResolutionX = cacheEntry.getSubpixelResolutionX(); + int subpixelResolutionY = cacheEntry.getSubpixelResolutionY(); + float glyphX = advX; + float glyphY = advY; if (glyphSet == XRGlyphCache.BGRA_GLYPH_SET) { /* BGRA glyphs store pointers to BGRAGlyphInfo * struct instead of glyph index */ @@ -109,8 +113,19 @@ public final class XRTextRenderer extends GlyphListPipe { (int) (cacheEntry.getBgraGlyphInfoPtr() >> 32)); eltList.getGlyphs().addInt( (int) cacheEntry.getBgraGlyphInfoPtr()); - } else { + } else if (subpixelResolutionX == 1 && subpixelResolutionY == 1) { eltList.getGlyphs().addInt(cacheEntry.getGlyphID()); + } else { + glyphX += 0.5f / subpixelResolutionX - 0.5f; + glyphY += 0.5f / subpixelResolutionY - 0.5f; + int x = ((int) Math.floor(glyphX * + (float) subpixelResolutionX)) % subpixelResolutionX; + if (x < 0) x += subpixelResolutionX; + int y = ((int) Math.floor(glyphY * + (float) subpixelResolutionY)) % subpixelResolutionY; + if (y < 0) y += subpixelResolutionY; + eltList.getGlyphs().addInt(cacheEntry.getGlyphID() + + x + y * subpixelResolutionX); } containsLCDGlyphs |= (glyphSet == glyphCache.lcdGlyphSet); @@ -134,8 +149,8 @@ public final class XRTextRenderer extends GlyphListPipe { if (gl.usePositions()) { // In this case advX only stores rounding errors - float x = positions[i * 2] + advX; - float y = positions[i * 2 + 1] + advY; + float x = positions[i * 2] + glyphX; + float y = positions[i * 2 + 1] + glyphY; posX = (int) Math.floor(x); posY = (int) Math.floor(y); advX -= cacheEntry.getXOff(); @@ -149,8 +164,8 @@ public final class XRTextRenderer extends GlyphListPipe { * later. This way rounding-error can be corrected, and * is required to be consistent with the software loops. */ - posX = (int) Math.floor(advX); - posY = (int) Math.floor(advY); + posX = (int) Math.floor(glyphX); + posY = (int) Math.floor(glyphY); // Advance of ELT = difference between stored relative // positioning information and required float. diff --git a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java index 402b917526ba..afb39b081ea5 100644 --- a/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java +++ b/src/java.desktop/unix/classes/sun/java2d/xr/XRBackendNative.java @@ -250,13 +250,23 @@ public final class XRBackendNative implements XRBackend { return glyphInfoPtrs; } + private static int countTotalSubglyphs(List cacheEntries) { + int totalSubglyphs = 0; + for (int i = 0; i < cacheEntries.size(); i++) { + XRGlyphCacheEntry entry = cacheEntries.get(i); + totalSubglyphs += entry.getSubpixelResolutionX() * + entry.getSubpixelResolutionY(); + } + return totalSubglyphs; + } + @Override public void XRenderAddGlyphs(int glyphSet, GlyphList gl, List cacheEntries, byte[] pixelData) { long[] glyphInfoPtrs = getGlyphInfoPtrs(cacheEntries); - XRAddGlyphsNative(glyphSet, glyphInfoPtrs, - glyphInfoPtrs.length, pixelData, pixelData.length); + XRAddGlyphsNative(glyphSet, glyphInfoPtrs, glyphInfoPtrs.length, + pixelData, pixelData.length, countTotalSubglyphs(cacheEntries)); } @Override @@ -268,7 +278,8 @@ public final class XRBackendNative implements XRBackend { long[] glyphInfoPtrs, int glyphCnt, byte[] pixelData, - int pixelDataLength); + int pixelDataLength, + int totalSubglyphs); private static native void XRFreeGlyphsNative(int glyphSet, int[] gids, int idCnt); diff --git a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c index b2e22faa5224..d165533712f2 100644 --- a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c +++ b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c @@ -758,19 +758,20 @@ JNIEXPORT void JNICALL Java_sun_java2d_xr_XRBackendNative_XRAddGlyphsNative (JNIEnv *env, jclass cls, jint glyphSet, jlongArray glyphInfoPtrsArray, jint glyphCnt, - jbyteArray pixelDataArray, int pixelDataLength) { + jbyteArray pixelDataArray, jint pixelDataLength, + jint subglyphs) { jlong *glyphInfoPtrs; unsigned char *pixelData; - int i; + int i, j; if (MAX_PAYLOAD / (sizeof(XGlyphInfo) + sizeof(Glyph)) - < (unsigned)glyphCnt) { - /* glyphCnt too big, payload overflow */ + < (unsigned)subglyphs) { + /* subglyphs too big, payload overflow */ return; } - XGlyphInfo *xginfo = (XGlyphInfo *) malloc(sizeof(XGlyphInfo) * glyphCnt); - Glyph *gid = (Glyph *) malloc(sizeof(Glyph) * glyphCnt); + XGlyphInfo *xginfo = (XGlyphInfo *) malloc(sizeof(XGlyphInfo) * subglyphs); + Glyph *gid = (Glyph *) malloc(sizeof(Glyph) * subglyphs); if (xginfo == NULL || gid == NULL) { if (xginfo != NULL) { @@ -800,24 +801,29 @@ Java_sun_java2d_xr_XRBackendNative_XRAddGlyphsNative return; } + int outputGlyph = 0; for (i=0; i < glyphCnt; i++) { GlyphInfo *jginfo = (GlyphInfo *) jlong_to_ptr(glyphInfoPtrs[i]); - // 'jginfo->cellInfo' is of type 'void*' - // (see definition of 'GlyphInfo' in fontscalerdefs.h) - // 'Glyph' is typedefed to 'unsigned long' - // (see http://www.x.org/releases/X11R7.7/doc/libXrender/libXrender.txt) - // Maybe we should assert that (sizeof(void*) == sizeof(Glyph)) ? - gid[i] = (Glyph) (jginfo->cellInfo); - xginfo[i].x = (-jginfo->topLeftX); - xginfo[i].y = (-jginfo->topLeftY); - xginfo[i].width = jginfo->width; - xginfo[i].height = jginfo->height; - xginfo[i].xOff = round(jginfo->advanceX); - xginfo[i].yOff = round(jginfo->advanceY); + int images = jginfo->subpixelResolutionX * jginfo->subpixelResolutionY; + for (j=0; j < images; j++) { + // 'jginfo->cellInfo' is of type 'void*' + // (see definition of 'GlyphInfo' in fontscalerdefs.h) + // 'Glyph' is typedefed to 'unsigned long' + // (see http://www.x.org/releases/X11R7.7/doc/libXrender/libXrender.txt) + // Maybe we should assert that (sizeof(void*) == sizeof(Glyph)) ? + gid[outputGlyph] = (Glyph) (jginfo->cellInfo) + j; + xginfo[outputGlyph].x = (-jginfo->topLeftX); + xginfo[outputGlyph].y = (-jginfo->topLeftY); + xginfo[outputGlyph].width = jginfo->width; + xginfo[outputGlyph].height = jginfo->height; + xginfo[outputGlyph].xOff = round(jginfo->advanceX); + xginfo[outputGlyph].yOff = round(jginfo->advanceY); + outputGlyph++; + } } - XRenderAddGlyphs(awt_display, glyphSet, &gid[0], &xginfo[0], glyphCnt, + XRenderAddGlyphs(awt_display, glyphSet, &gid[0], &xginfo[0], subglyphs, (const char*)pixelData, pixelDataLength); (*env)->ReleasePrimitiveArrayCritical(env, glyphInfoPtrsArray, glyphInfoPtrs, JNI_ABORT);