OMG BEST FONT RENDERING EVER FUUUUCK

This commit is contained in:
bigguy345 committed 2026-03-12 17:26:26 +02:00
1 parent be4aa2592a
commit 28d7b02350
2 files changed
+139 -35

No files matched your search

@@ -1,6 +1,7 @@
package noppes.npcs.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GLAllocation;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
@@ -91,6 +92,21 @@ public class GlyphCache {
*/
private boolean antiAliasEnabled = false;
/**
* The current Minecraft GUI scale factor (1-4). Glyphs are rasterized at fontSize * guiScaleFactor
* so the atlas has enough texel density for the actual display resolution. This prevents
* GL_LINEAR texture filtering from diluting alpha values at high resolutions.
*/
private int guiScaleFactor = 1;
/**
* Return the GUI scale factor used for glyph rasterization. StringCache uses this
* to compute an effective render scale that compensates for the larger atlas images.
*/
int getGuiScaleFactor() {
return guiScaleFactor;
}
/**
* Temporary image for rendering a string to and then extracting the glyph images from.
@@ -103,6 +119,7 @@ public class GlyphCache {
private Graphics2D stringGraphics;
/**
* All font glyphs are packed inside this image and are then loaded from here into an OpenGL texture.
*/
@@ -280,6 +297,7 @@ public class GlyphCache {
fontSize = size;
antiAliasEnabled = antiAlias;
guiScaleFactor = queryGuiScaleFactor();
setRenderingHints();
}
@@ -295,10 +313,50 @@ public class GlyphCache {
fontSize = size;
antiAliasEnabled = antiAlias;
guiScaleFactor = queryGuiScaleFactor();
setRenderingHints();
}
}
private static int queryGuiScaleFactor() {
try {
Minecraft mc = Minecraft.getMinecraft();
if (mc != null && mc.displayWidth > 0 && mc.displayHeight > 0) {
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
return Math.max(1, sr.getScaleFactor());
}
} catch (Exception ignored) {
}
return 1;
}
/**
* Check if the GUI scale factor has changed (e.g. due to window resize) and invalidate the glyph
* cache if so. Glyphs must be re-rasterized at the new density to prevent GL_LINEAR filtering
* from diluting alpha at high display resolutions.
*
* @return true if the scale factor changed and glyphs were invalidated
*/
boolean checkAndUpdateScaleFactor() {
int newScale = queryGuiScaleFactor();
if (newScale != guiScaleFactor) {
guiScaleFactor = newScale;
invalidateGlyphCache();
return true;
}
return false;
}
private void invalidateGlyphCache() {
glyphCache.clear();
fontCache.clear();
cachePosX = GLYPH_BORDER;
cachePosY = GLYPH_BORDER;
cacheLineHeight = 0;
allocateGlyphCacheTexture();
allocateStringImage(STRING_WIDTH, STRING_HEIGHT);
}
int fontHeight(String s) {
Font font = lookupFont(s.toCharArray(), 0, s.length(), 0);
return (int) (font.getLineMetrics(s, this.fontRenderContext).getHeight());
@@ -338,6 +396,9 @@ public class GlyphCache {
* @return an OpenType font capable of displaying at least the first character at the start position in text
*/
Font lookupFont(char text[], int start, int limit, int style) {
/* Rasterize at scaled size so atlas texel density matches display resolution */
int renderSize = fontSize * guiScaleFactor;
/* Try using an already known base font; the first font in usedFonts list is the one set with setDefaultFont() */
Iterator<Font> iterator = usedFonts.iterator();
while (iterator.hasNext()) {
@@ -345,7 +406,7 @@ public class GlyphCache {
Font font = iterator.next();
if (font.canDisplayUpTo(text, start, limit) != start) {
/* Return a font instance of the proper point size and style; usedFonts has only 1pt sized plain style fonts */
return font.deriveFont(style, fontSize);
return font.deriveFont(style, renderSize);
}
}
@@ -360,7 +421,7 @@ public class GlyphCache {
usedFonts.add(font);
/* Return a font instance of the proper point size and style; allFonts has only 1pt sized plain style fonts */
return font.deriveFont(style, fontSize);
return font.deriveFont(style, renderSize);
}
}
@@ -368,7 +429,7 @@ public class GlyphCache {
Font font = usedFonts.get(0);
/* Return a font instance of the proper point size and style; usedFonts only 1pt sized plain style fonts */
return font.deriveFont(style, fontSize);
return font.deriveFont(style, renderSize);
}
/**
@@ -460,7 +521,7 @@ public class GlyphCache {
/* Expand bounds to account for hinting overflow */
vectorBounds.grow(GLYPH_PADDING, GLYPH_PADDING);
/* Enlage the stringImage if it is too small to store the entire rendered string */
/* Enlarge the stringImage if it is too small to store the entire rendered string */
if (stringImage == null || vectorBounds.width > stringImage.getWidth() || vectorBounds.height > stringImage.getHeight()) {
int width = Math.max(vectorBounds.width, stringImage.getWidth());
int height = Math.max(vectorBounds.height, stringImage.getHeight());
@@ -615,16 +676,19 @@ public class GlyphCache {
stringGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
antiAliasEnabled ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
if (antiAliasEnabled) {
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
} else {
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
// if (antiAliasEnabled) {
// stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING,
// RenderingHints.VALUE_RENDER_QUALITY);
// } else {
// stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
// }
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
stringGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
fontRenderContext = stringGraphics.getFontRenderContext();
@@ -73,8 +73,9 @@ public class StringCache {
*/
private static final int STRIKETHROUGH_THICKNESS = 2;
// BetterFonts renders glyphs at 2x internal resolution; this scale converts to GUI coords
private static final float RENDER_SCALE = 0.5F;
// BetterFonts base scale: maps internal 2x glyph coords to GUI coords.
// With scale-aware rasterization, the effective scale becomes BASE_RENDER_SCALE / guiScaleFactor.
private static final float BASE_RENDER_SCALE = 0.5F;
private static final boolean FONT_DEBUG_LOG = Boolean.getBoolean("cnpc.font.debug");
private static final boolean FONT_DEBUG_DISABLE_INK_OFFSET = Boolean.getBoolean("cnpc.font.debug.disableInkOffset");
@@ -90,8 +91,14 @@ public class StringCache {
return c == '"' ? "\\\"" : String.valueOf(c);
}
private static float pixelSnap(float coord) {
return (float) Math.floor(coord * 2.0F + 0.5F) * RENDER_SCALE;
private float renderScale() {
return BASE_RENDER_SCALE / glyphCache.getGuiScaleFactor();
}
private float pixelSnap(float coord) {
float scale = renderScale();
float invScale = 1.0F / scale;
return (float) Math.floor(coord * invScale + 0.5F) * scale;
}
private int baselineOffset = BASELINE_OFFSET;
@@ -468,7 +475,8 @@ public class StringCache {
if (g.y > maxY)
maxY = g.y;
}
fontHeight = (int) ((maxY + height) / 2.0F - minY / 2.0F);
int scaleFactor = glyphCache.getGuiScaleFactor();
fontHeight = (int) (((maxY + height) - minY) / 2.0F / scaleFactor);
updateFontMetrics();
}
@@ -477,11 +485,36 @@ public class StringCache {
float ascent = lm.getAscent();
float descent = lm.getDescent();
baselineOffset = (int) (ascent * RENDER_SCALE + 0.5f);
/*
* baselineOffset is added directly to startY (GUI pixels) at render time, so it must be a
* scale-invariant GUI-pixel value. The constant BASELINE_OFFSET (7) is correct for the default
* 18pt font and remains stable across all GUI scale factors because it operates in GUI coordinate
* space — independent of the glyph rasterization scale.
*
* A dynamic formula like (ascent - padding) * renderScale is fragile: ascent comes from the
* scaled font (fontSize * scaleFactor) and rounding/font-metric variations across scales can
* produce values 1-2 pixels too large, visibly shifting text down within its highlight box.
*/
baselineOffset = BASELINE_OFFSET;
/*
* underlineOffset and strikethroughOffset are multiplied by renderScale() at draw time
* (same as glyph.x/glyph.y), so they must be in glyph-space pixels. These scale correctly
* because ascent/descent are proportional to the rasterization size.
*/
underlineOffset = (int) (descent * 0.25f + 0.5f);
strikethroughOffset = -(int) (ascent * 0.35f + 0.5f);
}
private void checkForScaleChange() {
if (glyphCache.checkAndUpdateScaleFactor()) {
weakRefCache.clear();
stringCache.clear();
cacheDightGlyphs();
updateHeight();
}
}
/**
* Pre-cache the ASCII digits to allow for fast glyph substitution. Called once from the constructor and called any time the font selection
* changes at runtime via setDefaultFont().
@@ -518,6 +551,8 @@ public class StringCache {
return 0;
}
/* Detect window resize: if GUI scale factor changed, invalidate all caches and re-rasterize */
checkForScaleChange();
/* Make sure the entire string is cached before rendering and return its glyph representation */
Entry entry = cacheString(str);
@@ -615,10 +650,11 @@ public class StringCache {
if (FONT_DEBUG_DISABLE_INK_OFFSET) {
glyphX = glyph.x;
}
float x1 = pixelSnap(startX + (glyphX) * RENDER_SCALE);
float x2 = pixelSnap(startX + (glyphX + texture.width) * RENDER_SCALE);
float y1 = pixelSnap(startY + (glyph.y) * RENDER_SCALE);
float y2 = pixelSnap(startY + (glyph.y + texture.height) * RENDER_SCALE);
float rs = renderScale();
float x1 = pixelSnap(startX + (glyphX) * rs);
float x2 = pixelSnap(startX + (glyphX + texture.width) * rs);
float y1 = pixelSnap(startY + (glyph.y) * rs);
float y2 = pixelSnap(startY + (glyph.y + texture.height) * rs);
if (FONT_DEBUG_LOG && isSpacingDebugGlyph(c)) {
LogWriter.info("[StringCache glyph-debug] char='" + debugGlyphLabel(c) + "' idx=" + glyph.stringIndex +
@@ -667,10 +703,11 @@ public class StringCache {
/* Draw underline under glyph if the style is enabled */
if ((renderStyle & ColorCode.UNDERLINE) != 0) {
/* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * RENDER_SCALE);
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * RENDER_SCALE);
float y1 = pixelSnap(startY + (underlineOffset) * RENDER_SCALE);
float y2 = pixelSnap(startY + (underlineOffset + UNDERLINE_THICKNESS) * RENDER_SCALE);
float urs = renderScale();
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * urs);
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * urs);
float y1 = pixelSnap(startY + (underlineOffset) * urs);
float y2 = pixelSnap(startY + (underlineOffset + UNDERLINE_THICKNESS) * urs);
tessellator.addVertex(x1, y1, 0);
tessellator.addVertex(x1, y2, 0);
@@ -681,10 +718,11 @@ public class StringCache {
/* Draw strikethrough in the middle of glyph if the style is enabled */
if ((renderStyle & ColorCode.STRIKETHROUGH) != 0) {
/* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * RENDER_SCALE);
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * RENDER_SCALE);
float y1 = pixelSnap(startY + (strikethroughOffset) * RENDER_SCALE);
float y2 = pixelSnap(startY + (strikethroughOffset + STRIKETHROUGH_THICKNESS) * RENDER_SCALE);
float srs = renderScale();
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * srs);
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * srs);
float y1 = pixelSnap(startY + (strikethroughOffset) * srs);
float y2 = pixelSnap(startY + (strikethroughOffset + STRIKETHROUGH_THICKNESS) * srs);
tessellator.addVertex(x1, y1, 0);
tessellator.addVertex(x1, y2, 0);
@@ -700,7 +738,7 @@ public class StringCache {
/* Return total horizontal advance (slightly wider than the bounding box, but close enough for centering strings) */
return entry.advance / 2;
return (int) (entry.advance * renderScale());
}
/**
@@ -715,11 +753,13 @@ public class StringCache {
return 0;
}
checkForScaleChange();
/* Make sure the entire string is cached and rendered since it will probably be used again in a renderString() call */
Entry entry = cacheString(str);
/* Return total horizontal advance (slightly wider than the bounding box, but close enough for centering strings) */
return entry.advance / 2;
return (int) (entry.advance * renderScale());
}
/**
@@ -738,8 +778,8 @@ public class StringCache {
return 0;
}
/* Convert the width from GUI coordinate system to pixels */
width += width;
/* Convert the width from GUI coordinate system to internal glyph pixels */
width = (int) (width / renderScale());
/* The glyph array for a string is sorted by the string's logical character position */
Glyph glyphs[] = cacheString(str).glyphs;