Skip to content

Commit d3d2817

Browse files
committed
fix(fonts): fix not support CJK character sets and optimized performance
1 parent 36f3268 commit d3d2817

File tree

3 files changed

+180
-57
lines changed

3 files changed

+180
-57
lines changed

src/main/java/meteordevelopment/meteorclient/renderer/Texture.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ public void upload(ByteBuffer buffer) {
4747
image.close();
4848
}
4949

50+
public void uploadRegion(ByteBuffer buffer, int srcX, int srcY, int width, int height) {
51+
if (width * height > (getWidth() * getHeight()) / 4) {
52+
upload(buffer);
53+
return;
54+
}
55+
56+
var image = getImage();
57+
58+
// cp only dirty region
59+
int texWidth = getWidth();
60+
long srcAddr = MemoryUtil.memAddress(buffer);
61+
long dstAddr = image.imageId();
62+
63+
for (int y = 0; y < height; y++) {
64+
long srcOffset = srcAddr + ((srcY + y) * texWidth + srcX);
65+
long dstOffset = dstAddr + ((srcY + y) * texWidth + srcX);
66+
MemoryUtil.memCopy(srcOffset, dstOffset, width);
67+
}
68+
69+
RenderSystem.getDevice().createCommandEncoder().writeToTexture(glTexture, image);
70+
71+
image.close();
72+
}
73+
5074
private @NotNull NativeImage getImage() {
5175
NativeImage.Format imageFormat = switch (glTexture.getFormat()) {
5276
case RGBA8 -> NativeImage.Format.RGBA;

src/main/java/meteordevelopment/meteorclient/renderer/text/CustomTextRenderer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ public boolean isBuilding() {
122122
public void end() {
123123
if (!building) throw new RuntimeException("CustomTextRenderer.end() called without calling begin()");
124124

125+
for (Font f : fonts) {
126+
f.updateTextureIfNeeded();
127+
}
128+
125129
if (!scaleOnly) {
126130
mesh.end();
127131

src/main/java/meteordevelopment/meteorclient/renderer/text/Font.java

Lines changed: 152 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -26,76 +26,129 @@ public class Font {
2626
private final Int2ObjectOpenHashMap<CharData> charMap = new Int2ObjectOpenHashMap<>();
2727
private static final int size = 2048;
2828

29+
private final STBTTFontinfo fontInfo;
30+
private final ByteBuffer fontBuffer;
31+
private final ByteBuffer bitmap;
32+
private int currentX = 0;
33+
private int currentY = 0;
34+
private int rowHeight = 0;
35+
private static final int PADDING = 2;
36+
37+
// track dirty regions for partial texture updates
38+
private int dirtyMinX = Integer.MAX_VALUE;
39+
private int dirtyMinY = Integer.MAX_VALUE;
40+
private int dirtyMaxX = 0;
41+
private int dirtyMaxY = 0;
42+
private boolean needsTextureUpdate = false;
43+
2944
public Font(ByteBuffer buffer, int height) {
3045
this.height = height;
46+
this.fontBuffer = buffer;
3147

32-
// Initialize font
33-
STBTTFontinfo fontInfo = STBTTFontinfo.create();
48+
// init
49+
fontInfo = STBTTFontinfo.create();
3450
STBTruetype.stbtt_InitFont(fontInfo, buffer);
3551

36-
// Allocate buffers
37-
ByteBuffer bitmap = BufferUtils.createByteBuffer(size * size);
38-
STBTTPackedchar.Buffer[] cdata = {
39-
STBTTPackedchar.create(95), // Basic Latin
40-
STBTTPackedchar.create(96), // Latin 1 Supplement
41-
STBTTPackedchar.create(128), // Latin Extended-A
42-
STBTTPackedchar.create(144), // Greek and Coptic
43-
STBTTPackedchar.create(256), // Cyrillic
44-
STBTTPackedchar.create(1) // infinity symbol
45-
};
46-
47-
// create and initialise packing context
48-
STBTTPackContext packContext = STBTTPackContext.create();
49-
STBTruetype.stbtt_PackBegin(packContext, bitmap, size, size, 0 ,1);
50-
51-
// create the pack range, populate with the specific packing ranges
52-
STBTTPackRange.Buffer packRange = STBTTPackRange.create(cdata.length);
53-
packRange.put(STBTTPackRange.create().set(height, 32, null, 95, cdata[0], (byte) 2, (byte) 2));
54-
packRange.put(STBTTPackRange.create().set(height, 160, null, 96, cdata[1], (byte) 2, (byte) 2));
55-
packRange.put(STBTTPackRange.create().set(height, 256, null, 128, cdata[2], (byte) 2, (byte) 2));
56-
packRange.put(STBTTPackRange.create().set(height, 880, null, 144, cdata[3], (byte) 2, (byte) 2));
57-
packRange.put(STBTTPackRange.create().set(height, 1024, null, 256, cdata[4], (byte) 2, (byte) 2));
58-
packRange.put(STBTTPackRange.create().set(height, 8734, null, 1, cdata[5], (byte) 2, (byte) 2)); // lol
59-
packRange.flip();
60-
61-
// write and finish
62-
STBTruetype.stbtt_PackFontRanges(packContext, buffer, 0, packRange);
63-
STBTruetype.stbtt_PackEnd(packContext);
64-
65-
// Create texture object and get font scale
52+
bitmap = BufferUtils.createByteBuffer(size * size);
53+
6654
texture = new Texture(size, size, TextureFormat.RED8, FilterMode.LINEAR, FilterMode.LINEAR);
67-
texture.upload(bitmap);
6855
scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, height);
6956

70-
// Get font vertical ascent
57+
// get font vertical ascent
7158
try (MemoryStack stack = MemoryStack.stackPush()) {
7259
IntBuffer ascent = stack.mallocInt(1);
7360
STBTruetype.stbtt_GetFontVMetrics(fontInfo, ascent, null, null);
7461
this.ascent = ascent.get(0);
7562
}
7663

77-
for (int i = 0; i < cdata.length; i++) {
78-
STBTTPackedchar.Buffer cbuf = cdata[i];
79-
int offset = packRange.get(i).first_unicode_codepoint_in_range();
80-
81-
for (int j = 0; j < cbuf.capacity(); j++) {
82-
STBTTPackedchar packedChar = cbuf.get(j);
83-
84-
float ipw = 1f / size; // pixel width and height
85-
float iph = 1f / size;
86-
87-
charMap.put(j + offset, new CharData(
88-
packedChar.xoff(),
89-
packedChar.yoff(),
90-
packedChar.xoff2(),
91-
packedChar.yoff2(),
92-
packedChar.x0() * ipw,
93-
packedChar.y0() * iph,
94-
packedChar.x1() * ipw,
95-
packedChar.y1() * iph,
96-
packedChar.xadvance()
97-
));
64+
// preload only basic ascii
65+
preloadCharacterRange(32, 126);
66+
preloadCharacterRange(160, 255);
67+
68+
texture.upload(bitmap);
69+
}
70+
71+
private void preloadCharacterRange(int start, int end) {
72+
for (int cp = start; cp <= end; cp++) {
73+
loadCharacter(cp);
74+
}
75+
}
76+
77+
private void loadCharacter(int codepoint) {
78+
if (charMap.containsKey(codepoint)) return;
79+
80+
try (MemoryStack stack = MemoryStack.stackPush()) {
81+
IntBuffer width = stack.mallocInt(1);
82+
IntBuffer height = stack.mallocInt(1);
83+
IntBuffer xoff = stack.mallocInt(1);
84+
IntBuffer yoff = stack.mallocInt(1);
85+
86+
ByteBuffer charBitmap = STBTruetype.stbtt_GetCodepointBitmap(
87+
fontInfo, scale, scale, codepoint, width, height, xoff, yoff
88+
);
89+
90+
if (charBitmap == null) {
91+
if (codepoint != 32 && charMap.containsKey(32)) {
92+
charMap.put(codepoint, charMap.get(32));
93+
}
94+
return;
95+
}
96+
97+
int w = width.get(0);
98+
int h = height.get(0);
99+
100+
if (currentX + w + PADDING > size) {
101+
currentX = 0;
102+
currentY += rowHeight + PADDING;
103+
rowHeight = 0;
104+
}
105+
106+
// if we're out of texture space
107+
if (currentY + h + PADDING > size) {
108+
STBTruetype.stbtt_FreeBitmap(charBitmap, 0L);
109+
return;
98110
}
111+
112+
// cp char bitmap to main bitmap
113+
for (int y = 0; y < h; y++) {
114+
for (int x = 0; x < w; x++) {
115+
int srcIdx = y * w + x;
116+
int dstIdx = (currentY + y) * size + (currentX + x);
117+
bitmap.put(dstIdx, charBitmap.get(srcIdx));
118+
}
119+
}
120+
121+
// Update dirty region bounds
122+
dirtyMinX = Math.min(dirtyMinX, currentX);
123+
dirtyMinY = Math.min(dirtyMinY, currentY);
124+
dirtyMaxX = Math.max(dirtyMaxX, currentX + w);
125+
dirtyMaxY = Math.max(dirtyMaxY, currentY + h);
126+
127+
IntBuffer advanceWidth = stack.mallocInt(1);
128+
STBTruetype.stbtt_GetCodepointHMetrics(fontInfo, codepoint, advanceWidth, null);
129+
130+
float ipw = 1f / size;
131+
float iph = 1f / size;
132+
133+
float u0 = currentX * ipw;
134+
float v0 = currentY * iph;
135+
float u1 = (currentX + w) * ipw;
136+
float v1 = (currentY + h) * iph;
137+
138+
// save char data
139+
charMap.put(codepoint, new CharData(
140+
xoff.get(0),
141+
yoff.get(0),
142+
xoff.get(0) + w,
143+
yoff.get(0) + h,
144+
u0, v0, u1, v1,
145+
advanceWidth.get(0) * scale
146+
));
147+
148+
currentX += w + PADDING;
149+
rowHeight = Math.max(rowHeight, h);
150+
151+
STBTruetype.stbtt_FreeBitmap(charBitmap, 0L);
99152
}
100153
}
101154

@@ -104,8 +157,17 @@ public double getWidth(String string, int length) {
104157

105158
for (int i = 0; i < length; i++) {
106159
int cp = string.charAt(i);
160+
161+
// load not loaded char
162+
if (!charMap.containsKey(cp)) {
163+
loadCharacter(cp);
164+
}
165+
107166
CharData c = charMap.get(cp);
108-
if (c == null) c = charMap.get(32);
167+
if (c == null) {
168+
c = charMap.get(32); // fallback
169+
if (c == null) continue;
170+
}
109171

110172
width += c.xAdvance;
111173
}
@@ -123,10 +185,23 @@ public double render(MeshBuilder mesh, String string, double x, double y, Color
123185
int length = string.length();
124186
mesh.ensureCapacity(length * 4, length * 6);
125187

188+
// load all missing chars
189+
for (int i = 0; i < length; i++) {
190+
int cp = string.charAt(i);
191+
if (!charMap.containsKey(cp)) {
192+
loadCharacter(cp);
193+
needsTextureUpdate = true;
194+
}
195+
}
196+
197+
// render chars
126198
for (int i = 0; i < length; i++) {
127199
int cp = string.charAt(i);
128200
CharData c = charMap.get(cp);
129-
if (c == null) c = charMap.get(32);
201+
if (c == null) {
202+
c = charMap.get(32);
203+
if (c == null) continue;
204+
}
130205

131206
mesh.quad(
132207
mesh.vec2(x + c.x0 * scale, y + c.y0 * scale).vec2(c.u0, c.v0).color(color).next(),
@@ -141,5 +216,25 @@ public double render(MeshBuilder mesh, String string, double x, double y, Color
141216
return x;
142217
}
143218

219+
public void updateTextureIfNeeded() {
220+
if (needsTextureUpdate) {
221+
if (dirtyMinX < dirtyMaxX && dirtyMinY < dirtyMaxY) {
222+
int width = dirtyMaxX - dirtyMinX;
223+
int height = dirtyMaxY - dirtyMinY;
224+
texture.uploadRegion(bitmap, dirtyMinX, dirtyMinY, width, height);
225+
} else {
226+
texture.upload(bitmap);
227+
}
228+
229+
needsTextureUpdate = false;
230+
231+
// reset dirty region
232+
dirtyMinX = Integer.MAX_VALUE;
233+
dirtyMinY = Integer.MAX_VALUE;
234+
dirtyMaxX = 0;
235+
dirtyMaxY = 0;
236+
}
237+
}
238+
144239
private record CharData(float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float xAdvance) {}
145240
}

0 commit comments

Comments
 (0)