More work on the font system

This commit is contained in:
Speiger 2024-07-25 20:39:38 +02:00
parent 0139086858
commit 62b91f0f1d
15 changed files with 2148 additions and 1940 deletions

View File

@ -6,15 +6,20 @@ import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import com.google.gson.JsonObject;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.AssetManager;
import speiger.src.coreengine.assets.base.IAsset;
import speiger.src.coreengine.assets.base.IAssetPackage;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.GlythBaker;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
import speiger.src.coreengine.rendering.gui.font.providers.STBTrueTypeProvider;
import speiger.src.coreengine.rendering.guiOld.renderer.provider.BitmapFontProvider;
import speiger.src.coreengine.rendering.input.devices.FileDrop;
import speiger.src.coreengine.rendering.input.devices.Joystick;
import speiger.src.coreengine.rendering.input.devices.Keyboard;
@ -62,18 +67,23 @@ public class NewInputTest {
shaderTest.register();
assets.addListener(GLStateTracker.instance().shaders);
assets.addListener(GLStateTracker.TEXTURE_TRACKER);
assets.reload(Runnable::run, Runnable::run);
assets.reload();
System.out.println("Testing: "+GL.getCapabilities().OpenGL46);
System.out.println("Testing: "+Integer.divideUnsigned(-1, 255));
IFontProvider provider = STBTrueTypeProvider.create(AssetLocation.of("font/Roboto-Medium.ttf"), assets);
IFontProvider provider = STBTrueTypeProvider.create(AssetLocation.of("font/roboto/font.json"), assets);
BitmapFontProvider bit = loadProvider(AssetLocation.of("font/roboto.json"), 18.5F);
DynamicTexture texture = new DynamicTexture(512, 512, DynamicTexture.DEFAULT_PARAMETERS);
texture.fill(0, 0, 512, 512, -1);
texture.fill(0, 0, 256, 256, 255, 0, 0, 255);
texture.fill(256, 0, 256, 256, 0, 255, 0, 255);
texture.fill(256, 256, 256, 256, 255, 0, 0, 255);
texture.fill(0, 256, 256, 256, 0, 0, 255, 255);
int size = 512;
int half = size >> 1;
DynamicTexture texture = new DynamicTexture(size, size, DynamicTexture.DEFAULT_PARAMETERS);
texture.fill(0, 0, size, size, -1);
texture.fill(0, 0, half, half, 255, 0, 0, 255);
texture.fill(half, 0, half, half, 0, 255, 0, 255);
texture.fill(half, half, half, half, 255, 0, 0, 255);
texture.fill(0, half, half, half, 0, 0, 255, 255);
texture.process(true);
window.visible(true);
@ -81,23 +91,25 @@ public class NewInputTest {
if(T.key() == GLFW.GLFW_KEY_T) {
T.cancel();
texture.bind();
Drawable drawable = new Drawable(GLTextureFormat.RGBA, 256, 256);
drawable.fill(0, 0, 256, 256, 255, 255, 0, 255);
Drawable drawable = new Drawable(GLTextureFormat.RGBA, half, half);
drawable.fill(0, 0, half, half, 255, 255, 0, 255);
drawable.upload(192, 192, 64, 64, 128, 128);
drawable.close();
}
else if(T.key() == GLFW.GLFW_KEY_Z) {
T.cancel();
texture.bind();
provider.glythData("C".codePointAt(0), false).bake(new GlythBaker() {
GlythData data = provider.glythData("C".codePointAt(0), 0);
data.bake(new GlythBaker() {
@Override
public Glyth bake(IGlythSheetInfo info) {
int width = info.width();
int height = info.height();
info.upload(256 - (width >> 2), 256 - (height >> 2));
info.upload(half - (width >> 2), half - (height >> 2));
return null;
}
});
System.out.println("Testing: "+data.advance()+", "+bit.getCharacter("C".codePointAt(0), false).getXAdvance());
}
});
@ -132,6 +144,17 @@ public class NewInputTest {
manager.destroy();
}
private BitmapFontProvider loadProvider(AssetLocation location, float desiredSize)
{
try(IAsset asset = assets.getAsset(location))
{
JsonObject obj = asset.json();
return BitmapFontProvider.load(obj, desiredSize, null);
}
catch(Exception e) { e.printStackTrace(); }
return null;
}
public static class TestShader extends SimpleShader {
public TextureUniform texture = uniforms.addTexture("texture", 0);
public TestShader(IAssetProvider provider) {

View File

@ -42,10 +42,18 @@ public class AssetManager implements ICloseableAssetProvider
return reloadAssets(packages).reloadSelective(offthread, syncer, listeners);
}
public AssetManager reload() {
return reload(Runnable::run, Runnable::run);
}
public AssetManager reload(Executor offthread, Executor syncer) {
return reloadSelective(offthread, syncer, listeners);
}
public AssetManager reloadSelective(List<IReloadableAsset> listeners) {
return reloadSelective(Runnable::run, Runnable::run, listeners);
}
public AssetManager reloadSelective(Executor offthread, Executor syncer, List<IReloadableAsset> listeners) {
PackReloadingTask.reload(provider, listeners, offthread, syncer).join();
return this;

View File

@ -4,63 +4,68 @@ import java.util.List;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.GlythBaker;
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
public class FontGroup {
private static final int BOLD_FLAG = 1<<31;
private static final int TEXTURE_SIZE = 2048;
AssetLocation locations;
List<IFontProvider> providers;
Int2ObjectMap<Glyth> bakedGlyths = new Int2ObjectOpenHashMap<>();
Int2ObjectMap<GlythData> dataGlyths = new Int2ObjectOpenHashMap<>();
List<FontTexture> textures = new ObjectArrayList<>();
GlythBaker baker;
StyledFont[] cache = new StyledFont[] {new StyledFont(0), new StyledFont(1), new StyledFont(2), new StyledFont(3)};
public FontGroup(AssetLocation locations, List<IFontProvider> providers) {
public FontGroup(AssetLocation locations, List<IFontProvider> providers, GlythBaker baker) {
this.locations = locations;
this.providers = providers;
this.baker = baker;
}
public GlythData data(int codepoint, boolean bold) {
return dataGlyths.computeIfAbsent(codepoint | (bold ? 0 : BOLD_FLAG), this::compute);
public GlythData data(int codepoint, int style) {
return cache[style & 0x3].data(codepoint);
}
public Glyth glyth(int codepoint, boolean bold) {
return bakedGlyths.computeIfAbsent(codepoint | (bold ? 0 : BOLD_FLAG), this::bake);
public Glyth glyth(int codepoint, int style) {
return cache[style & 0x3].glyth(codepoint);
}
private GlythData compute(int codepoint) {
boolean bold = (codepoint & BOLD_FLAG) != 0;
codepoint &= ~BOLD_FLAG;
for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint, bold);
if(data != null) return data;
public void close() {
providers.forEach(IFontProvider::close);
providers.clear();
}
private class StyledFont {
final int style;
Int2ObjectMap<Glyth> bakedGlyths = new Int2ObjectOpenHashMap<>();
Int2ObjectMap<GlythData> dataGlyths = new Int2ObjectOpenHashMap<>();
public StyledFont(int style) {
this.style = style;
}
return null;
}
private Glyth bake(int codepoint) {
boolean bold = (codepoint & BOLD_FLAG) != 0;
codepoint &= ~BOLD_FLAG;
for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint, bold);
if(data != null) return data.bake(this::stitch);
public GlythData data(int codepoint) {
return dataGlyths.computeIfAbsent(codepoint, this::compute);
}
return null;
}
private Glyth stitch(IGlythSheetInfo info) {
for(int i = 0,m=textures.size();i<m;i++) {
Glyth glyth = textures.get(i).build(info);
if(glyth != null) return glyth;
public Glyth glyth(int codepoint) {
return bakedGlyths.computeIfAbsent(codepoint, this::bake);
}
private GlythData compute(int codepoint) {
for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint, style);
if(data != null) return data;
}
return null;
}
private Glyth bake(int codepoint) {
for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint, style);
if(data != null) return data.bake(baker);
}
return null;
}
FontTexture texture = new FontTexture(TEXTURE_SIZE, info.isColored());
textures.add(texture);
Glyth glyth = texture.build(info);
return glyth;
}
}

View File

@ -1,5 +1,49 @@
package speiger.src.coreengine.rendering.gui.font;
public class FontManager {
import java.util.List;
import java.util.Map;
import com.google.gson.JsonObject;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.assets.base.SteppedReloadableAsset;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, List<JsonObject>>> {
private static final int TEXTURE_SIZE = 2048;
List<FontTexture> textures = new ObjectArrayList<>();
@Override
public String getName() { return null; }
@Override
public void destroy() {
}
@Override
protected Map<AssetLocation, List<JsonObject>> prepare(IAssetProvider provider) {
return null;
}
@Override
protected void apply(Map<AssetLocation, List<JsonObject>> value, IAssetProvider provider) {
}
private Glyth stitch(IGlythSheetInfo info) {
for(int i = 0,m=textures.size();i<m;i++) {
Glyth glyth = textures.get(i).build(info);
if(glyth != null) return glyth;
}
FontTexture texture = new FontTexture(TEXTURE_SIZE, info.isColored());
textures.add(texture);
Glyth glyth = texture.build(info);
return glyth;
}
}

View File

@ -1,3 +1,6 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
public record Glyth(int texture, float minU, float minV, float maxU, float maxV) {}
public record Glyth(int texture, float minU, float minV, float maxU, float maxV) {
public static final Glyth EMPTY = new Glyth(-1, 0, 0, 0, 0);
}

View File

@ -2,8 +2,15 @@ package speiger.src.coreengine.rendering.gui.font.glyth;
public interface GlythData {
public float advance();
public default float shadowOffset() { return 1F; }
public Glyth bake(GlythBaker baker);
public static record EmptyGlythData(float advance) implements GlythData {
@Override
public Glyth bake(GlythBaker baker) { return Glyth.EMPTY; }
}
public static interface GlythBaker {
Glyth bake(IGlythSheetInfo info);
}

View File

@ -0,0 +1,38 @@
package speiger.src.coreengine.rendering.gui.font.providers;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
public class BitmapProivder implements IFontProvider {
@Override
public GlythData glythData(int codepoint, int style) {
return null;
}
@Override
public void close() {
}
private static class BitmapGlyth implements GlythData {
int minX;
int minY;
int maxX;
int maxY;
int glyth;
@Override
public float advance() {
return 0;
}
@Override
public Glyth bake(GlythBaker baker) {
return null;
}
}
}

View File

@ -3,7 +3,13 @@ package speiger.src.coreengine.rendering.gui.font.providers;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
public interface IFontProvider extends AutoCloseable {
public GlythData glythData(int codepoint, boolean bold);
public static final int REGULAR = 0;
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int ITALIC_BOLD = 3;
public GlythData glythData(int codepoint, int style);
@Override
void close();
}

View File

@ -8,6 +8,8 @@ import org.lwjgl.stb.STBTruetype;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import com.google.gson.JsonObject;
import speiger.src.collections.ints.sets.IntOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.coreengine.assets.AssetLocation;
@ -17,33 +19,51 @@ import speiger.src.coreengine.assets.parsers.NativeMemoryParser;
import speiger.src.coreengine.rendering.gui.font.FontTexture;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.EmptyGlythData;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
import speiger.src.coreengine.utils.helpers.JsonUtil;
public class STBTrueTypeProvider implements IFontProvider {
long data;
STBTTFontinfo info;
IntSet skip = new IntOpenHashSet();
float oversample;
float xOff;
float yOff;
float pointScale;
float ascent;
TrueTypeInstance[] instances;
public STBTrueTypeProvider(long data, STBTTFontinfo info, float size, float oversample, float xOff, float yOff, String skip) {
this.data = data;
this.info = info;
skip.codePoints().forEach(this.skip::add);
this.oversample = oversample;
this.xOff = xOff;
this.yOff = yOff;
pointScale = STBTruetype.stbtt_ScaleForPixelHeight(info, size * oversample);
int[] ascent = new int[1];
STBTruetype.stbtt_GetFontVMetrics(info, ascent, new int[1], new int[1]);
this.ascent = pointScale * ascent[0];
public STBTrueTypeProvider(TrueTypeInstance[] instances) {
this.instances = instances;
}
public static IFontProvider create(AssetLocation location, IAssetProvider provider) {
try(IAsset asset = provider.getAsset(location)) {
return create(asset.json(), provider);
}
catch(Exception e) { e.printStackTrace(); }
return null;
}
public static IFontProvider create(JsonObject data, IAssetProvider provider) {
TrueTypeInstance[] instances = new TrueTypeInstance[4];
instances[0] = create(0, data.getAsJsonObject("regular"), provider);
if(instances[0] == null) return null;
instances[1] = create(1, data.getAsJsonObject("bold"), provider);
instances[2] = create(2, data.getAsJsonObject("italic"), provider);
instances[3] = create(3, data.getAsJsonObject("bold_italic"), provider);
return new STBTrueTypeProvider(instances);
}
private static TrueTypeInstance create(int style, JsonObject obj, IAssetProvider provider) {
if(obj == null || !obj.has("file")) return null;
AssetLocation location = AssetLocation.of(obj.get("file").getAsString());
float size = JsonUtil.getOrDefault(obj, "size", 24F);
float oversample = JsonUtil.getOrDefault(obj, "oversample", 1F);
float shadowOffset = JsonUtil.getOrDefault(obj, "shadowOffset", 1F);
float xOff = 0;
float yOff = 0;
StringBuilder builder = new StringBuilder();
JsonObject shift = obj.getAsJsonObject("offset");
if(shift != null) {
xOff = JsonUtil.getOrDefault(shift, "x", 0);
yOff = JsonUtil.getOrDefault(shift, "y", 0);
}
JsonUtil.iterateValues(obj.get("skip"), T -> builder.append(T.getAsString()));
try(IAsset asset = provider.getAsset(location)) {
ByteBuffer buffer = asset.custom(NativeMemoryParser.INSTANCE);
STBTTFontinfo info = STBTTFontinfo.create();
@ -53,7 +73,7 @@ public class STBTrueTypeProvider implements IFontProvider {
info.free();
return null;
}
return new STBTrueTypeProvider(MemoryUtil.memAddress(buffer), info, 10.2F, 24F, 0, 0, "");
return new TrueTypeInstance(style, MemoryUtil.memAddress(buffer), info, size, oversample, xOff, yOff, shadowOffset, builder.toString());
}
catch(Exception e) {
e.printStackTrace();
@ -61,61 +81,103 @@ public class STBTrueTypeProvider implements IFontProvider {
return null;
}
@Override
public GlythData glythData(int codepoint, boolean bold) {
if(skip.contains(codepoint)) return null;
int glyth = STBTruetype.nstbtt_FindGlyphIndex(info.address(), codepoint);
if(glyth == 0) return null;
try(MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer left = stack.mallocInt(1);
IntBuffer bottom = stack.mallocInt(1);
IntBuffer right = stack.mallocInt(1);
IntBuffer top = stack.mallocInt(1);
IntBuffer advance = stack.mallocInt(1);
IntBuffer leftSideBearing = stack.mallocInt(1);
STBTruetype.stbtt_GetGlyphHMetrics(info, glyth, advance, leftSideBearing);
STBTruetype.stbtt_GetGlyphBitmapBoxSubpixel(info, glyth, pointScale, pointScale, xOff, yOff, left, bottom, right, top);
int minX = left.get(0);
int minY = -top.get(0);
int maxX = right.get(0);
int maxY = -bottom.get(0);
if(maxX - minX <= 0 || maxY - minY <= 0) return null;
return new STBGlyth(this, minX, minY, maxX, maxY, advance.get(0) * pointScale, leftSideBearing.get(0) * pointScale, glyth);
public static class TrueTypeInstance {
final int style;
long data;
final STBTTFontinfo info;
IntSet skip = new IntOpenHashSet();
final float oversample;
final float xOff;
final float yOff;
final float shadowOffset;
final float pointScale;
final float ascent;
public TrueTypeInstance(int style, long data, STBTTFontinfo info, float size, float oversample, float xOff, float yOff, float shadowOffset, String skip) {
this.style = style;
this.data = data;
this.info = info;
skip.codePoints().forEach(this.skip::add);
this.oversample = oversample;
this.xOff = xOff;
this.yOff = yOff;
this.shadowOffset = shadowOffset;
pointScale = STBTruetype.stbtt_ScaleForPixelHeight(info, size * oversample);
int[] ascent = new int[1];
STBTruetype.stbtt_GetFontVMetrics(info, ascent, new int[1], new int[1]);
this.ascent = pointScale * ascent[0];
}
public void free() {
if(data == 0L) return;
info.free();
MemoryUtil.nmemFree(data);
data = 0L;
}
public GlythData glythData(int codepoint) {
if(skip.contains(codepoint)) return null;
int glyth = STBTruetype.nstbtt_FindGlyphIndex(info.address(), codepoint);
if(glyth == 0) return null;
try(MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer left = stack.mallocInt(1);
IntBuffer bottom = stack.mallocInt(1);
IntBuffer right = stack.mallocInt(1);
IntBuffer top = stack.mallocInt(1);
IntBuffer advance = stack.mallocInt(1);
IntBuffer leftSideBearing = stack.mallocInt(1);
STBTruetype.stbtt_GetGlyphHMetrics(info, glyth, advance, leftSideBearing);
STBTruetype.stbtt_GetGlyphBitmapBoxSubpixel(info, glyth, pointScale, pointScale, xOff, yOff, left, bottom, right, top);
int minX = left.get(0);
int minY = -top.get(0);
int maxX = right.get(0);
int maxY = -bottom.get(0);
if(maxX - minX <= 0 || maxY - minY <= 0) return new EmptyGlythData(advance.get(0) * pointScale / oversample);
return new STBGlyth(this, minX, minY, maxX, maxY, advance.get(0) * pointScale, leftSideBearing.get(0) * pointScale, glyth);
}
catch(Throwable exception) {
exception.printStackTrace();
return null;
}
}
}
@Override
public GlythData glythData(int codepoint, int style) {
TrueTypeInstance instance = instances[style & 0x3];
return instance == null ? null : instance.glythData(codepoint);
}
@Override
public void close() {
if(data == 0L) return;
info.free();
MemoryUtil.nmemFree(data);
data = 0L;
if(instances == null) return;
for(int i = 0;i<4;i++) {
if(instances[i] == null) continue;
instances[i].free();
}
instances = null;
}
private static class STBGlyth implements GlythData {
final STBTrueTypeProvider owner;
final TrueTypeInstance owner;
final int width;
final int height;
// final float xOff;
// final float yOff;
final float advance;
final int glyth;
public STBGlyth(STBTrueTypeProvider owner, int minX, int minY, int maxX, int maxY, float advance, float leftPadding, int glyth) {
public STBGlyth(TrueTypeInstance owner, int minX, int minY, int maxX, int maxY, float advance, float leftPadding, int glyth) {
this.owner = owner;
this.width = maxX - minX;
this.height = maxY - minY;
this.advance = advance / owner.oversample;
// this.xOff = (leftPadding + minX + owner.xOff) / owner.oversample;
// this.yOff = (owner.ascent + maxY + owner.yOff) / owner.oversample;
this.glyth = glyth;
}
@Override
public float advance() {
return advance;
}
public float advance() { return advance; }
@Override
public float shadowOffset() { return owner.shadowOffset; }
@Override
public Glyth bake(GlythBaker baker) {
return baker.bake(new IGlythSheetInfo() {

View File

@ -90,7 +90,7 @@ public class BitmapFontProvider implements IFontProvider
}
@SuppressWarnings("unchecked")
public static IFontProvider load(JsonObject object, float desiredSize, AssetManager manager)
public static BitmapFontProvider load(JsonObject object, float desiredSize, AssetManager manager)
{
FontInfo info = new FontInfo(object.getAsJsonObject("info"));
float multiplier = info.setDesiredHeight(desiredSize);

View File

@ -25,7 +25,7 @@ public class STBTexture extends AbstractTexture
public STBTexture(AssetLocation location)
{
this.location = location;
loadTexture();
// loadTexture();
}
private void loadTexture()

View File

@ -70,6 +70,7 @@ public class JsonUtil {
}
public static void iterateValues(JsonElement element, Consumer<JsonPrimitive> result) {
if(element == null) return;
if(element.isJsonPrimitive()) result.accept(element.getAsJsonPrimitive());
else if(element.isJsonArray()) {
JsonArray array = element.getAsJsonArray();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
{
"type": "stb-ttf",
"regular": {
"file": "font/roboto/Roboto-Medium.ttf",
"size": 18.5,
"oversample": 2,
"shadowOffset": 1,
"skip": "",
"offset": { "x": 0, "y": 0 }
}
}