mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-25 18:00:50 +01:00
JBR-2910 Implemented extended glyph cache for Linux
(cherry picked from commit d488f716e7)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user