JBR-2910 Implemented extended glyph cache for Linux

(cherry picked from commit d488f716e7)
This commit is contained in:
Nikita Gubarkov
2021-01-22 06:53:41 +03:00
committed by jbrbot
parent 09c31553c5
commit 010e2658da
6 changed files with 173 additions and 69 deletions

View File

@@ -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);

View File

@@ -48,7 +48,6 @@ public final class XRGlyphCache implements GlyphDisposedListener {
XRCompositeManager maskBuffer;
HashMap<MutableInteger, XRGlyphCacheEntry> cacheMap = new HashMap<MutableInteger, XRGlyphCacheEntry>(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<Integer> freeGlyphIDs = new ArrayList<Integer>(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<Integer>[] freeIDsByCapacity = new List[]{
new ArrayList<>(255)
};
private int nextID = 1;
private int allocateID(int count) {
if (count <= freeIDsByCapacity.length) {
List<Integer> 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);
}
}
}

View File

@@ -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() {

View File

@@ -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.

View File

@@ -250,13 +250,23 @@ public final class XRBackendNative implements XRBackend {
return glyphInfoPtrs;
}
private static int countTotalSubglyphs(List<XRGlyphCacheEntry> 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<XRGlyphCacheEntry> 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);

View File

@@ -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);