Newest state of the font renderer

This commit is contained in:
Speiger 2024-08-02 00:11:20 +02:00
parent 2dae61adbf
commit 7a7d9c1fd0
26 changed files with 523 additions and 196 deletions

View File

@ -1,25 +1,27 @@
package speiger.src.coreengine;
import java.util.List;
import java.util.function.BiConsumer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import com.google.gson.JsonObject;
import speiger.src.collections.objects.lists.ObjectArrayList;
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.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.rendering.gui.font.Font;
import speiger.src.coreengine.rendering.gui.font.FontManager;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.GlythBaker;
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.glyth.MissingGlyth;
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;
@ -32,6 +34,8 @@ import speiger.src.coreengine.rendering.models.buffers.VertexArray;
import speiger.src.coreengine.rendering.shader.Shader;
import speiger.src.coreengine.rendering.shader.SimpleShader;
import speiger.src.coreengine.rendering.shader.uniform.base.TextureUniform;
import speiger.src.coreengine.rendering.shader.uniform.vec.Matrix4fUniform;
import speiger.src.coreengine.rendering.tesselation.buffer.IVertexBuilder;
import speiger.src.coreengine.rendering.tesselation.buffer.VertexBuilder;
import speiger.src.coreengine.rendering.tesselation.format.VertexTypes;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
@ -48,7 +52,9 @@ public class NewInputTest {
EventBus bus = new EventBus();
WindowManager manager = new WindowManager();
AssetManager assets = new AssetManager(List.of(IAssetPackage.of(IOUtils.getBaseLocation())));
FontManager fonts = new FontManager();
private Shader<TestShader> shaderTest = Shader.create(TestShader::new);
private Shader<GuiShader> guiShader = Shader.create(GuiShader::new);
public static void main(String[] args) {
@ -65,15 +71,17 @@ public class NewInputTest {
manager.addDevices(Mouse.INSTANCE, Keyboard.INSTANCE, Joystick.INSTANCE, FileDrop.INSTANCE);
Window window = manager.builder().title("Testing Engine").build();
shaderTest.register();
guiShader.register();
assets.addListener(GLStateTracker.instance().shaders);
assets.addListener(GLStateTracker.TEXTURE_TRACKER);
assets.addListener(fonts);
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/font.json"), assets);
BitmapFontProvider bit = loadProvider(AssetLocation.of("font/roboto.json"), 18.5F);
guiShader.get().proView.set(new Matrix4f().ortho(0, 0, window.width(), window.height(), 1000, -1000));
int size = 512;
int half = size >> 1;
@ -87,12 +95,10 @@ public class NewInputTest {
texture.fill(0, half, half, half, 0, 0, 255, 255);
texture.process(true);
window.visible(true);
var instance = bit.getCharacter("C".codePointAt(0), false);
System.out.println("Testing: "+instance.getWidth()+","+instance.getHeight());
bus.register(KeyEvent.Key.class, T -> {
if(T.key() == GLFW.GLFW_KEY_T) {
T.cancel();
texture.bind();
// texture.bind();
Drawable drawable = new Drawable(GLTextureFormat.RGBA, half, half);
drawable.fill(0, 0, half, half, 255, 255, 0, 255);
drawable.upload(base * 3, base * 3, base, base, base * 2, base * 2);
@ -101,8 +107,8 @@ public class NewInputTest {
else if(T.key() == GLFW.GLFW_KEY_Z) {
T.cancel();
System.out.println("Testing");
texture.bind();
UnbakedGlyth data = provider.glythData("C".codePointAt(0), 0);
// texture.bind();
GlythData data = provider.glythData("C".codePointAt(0), 0, 18.5F, 2F);
data.bake(new GlythBaker() {
@Override
public Glyth bake(IGlythSheetInfo info) {
@ -112,7 +118,20 @@ public class NewInputTest {
System.out.println("Test2: "+width+", "+height);
return null;
}
}, 1F, 1F);
});
}
else if(T.key() == GLFW.GLFW_KEY_U) {
MissingGlyth glyth = new MissingGlyth(18.5F, 4F);
glyth.bake(new GlythBaker() {
@Override
public Glyth bake(IGlythSheetInfo info) {
int width = info.width();
int height = info.height();
info.upload(half - (width >> 1), half - (height >> 1));
System.out.println("Test3: "+width+", "+height);
return null;
}
});
}
});
@ -125,7 +144,15 @@ public class NewInputTest {
builder.pos(-0.5F, 0.5F, 0).tex(0F, 0F).rgba(-1).endVertex();
builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
Font font = fonts.createFont(18.5F);
TestModel model = new TestModel(builder.getBytes());
TestModel[] guiModel = new TestModel[1];
List<GLDraw> draws = new ObjectArrayList<>();
font.drawText("Testing my Text", 50, 50, -1, new TexturedBuffer((K, V) -> {
draws.addAll(K);
guiModel[0] = new TestModel(V);
System.out.println("Testing: "+V.length+" bytes, "+K.size());
}));
GL11.glClearColor(0.2F, 0.55F, 0.66F, 1F);
while(!window.shouldClose()) {
@ -136,8 +163,16 @@ public class NewInputTest {
shaderTest.get().texture.set(texture);
model.bindArray();
GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), 6);
model.unbindArray();
guiModel[0].bindArray();
guiShader.bind();
for(GLDraw draw : draws) {
// System.out.println("Draw: "+draw.texture()+", "+draw.startVertex()+", "+draw.vertexCount());
guiShader.get().texture.set(draw.texture());
GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), draw.startVertex(), draw.vertexCount());
}
guiModel[0].unbindArray();
window.handleInput();
window.finishFrame();
try { Thread.sleep(100); }
@ -147,15 +182,56 @@ 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);
public static class TexturedBuffer implements Font.TexturedBuffer {
int previousId = -1;
int lastVertex = 0;
List<GLDraw> draws = new ObjectArrayList<>();
VertexBuilder builder;
BiConsumer<List<GLDraw>, byte[]> callbacks;
public TexturedBuffer(BiConsumer<List<GLDraw>, byte[]> callbacks) {
this.callbacks = callbacks;
}
catch(Exception e) { e.printStackTrace(); }
return null;
@Override
public IVertexBuilder builderForTexture(int textureId) {
if(builder == null) {
builder = new VertexBuilder(100000);
builder.start(GLMode.TRIANGLES, VertexTypes.TESTING);
previousId = textureId;
System.out.println("Texture: "+textureId);
}
else if(previousId != textureId) {
int count = builder.size() - lastVertex;
draws.add(new GLDraw(previousId, lastVertex, count));
lastVertex+=count;
previousId = textureId;
System.out.println("Texture: "+textureId);
}
return builder;
}
@Override
public void end() {
int count = builder.size() - lastVertex;
draws.add(new GLDraw(previousId, lastVertex, count));
callbacks.accept(draws, builder.getBytes());
}
}
public static record GLDraw(int texture, int startVertex, int vertexCount) {
}
public static class GuiShader extends SimpleShader {
public TextureUniform texture = uniforms.addTexture("texture", 0);
public Matrix4fUniform proView = uniforms.addMat("proViewMatrix", new Matrix4f());
public GuiShader(IAssetProvider provider) {
super(provider, "gui_shader", AssetLocation.of("shader/testGui/vertex.vs"), AssetLocation.of("shader/testGui/fragment.fs"), "in_position", "in_tex", "in_color");
}
}
public static class TestShader extends SimpleShader {

View File

@ -32,4 +32,8 @@ public record AssetFilter(String prefix, String extension) {
return provider.listAllAssets(prefix, T -> T.endsWith(extension));
}
public Map<AssetLocation, MultiAsset> multiRoot(IAssetProvider provider) {
return provider.listAllAssets(prefix, T -> T.endsWith(extension) && !T.isInFolder(prefix));
}
}

View File

@ -26,6 +26,10 @@ public final class AssetLocation implements Comparable<AssetLocation> {
public boolean isInFolder() { return location.contains("/"); }
public boolean isInFolder(String prefix) { return location.indexOf("/", prefix.length()+1) >= 0; }
public AssetLocation prefix(String prefix) {
return of(domain, prefix+"/"+location);
}
public AssetLocation subAsset(String subPath) {
return of(domain+":"+location+"/"+subPath);
}
@ -67,8 +71,40 @@ public final class AssetLocation implements Comparable<AssetLocation> {
return LOCATION.computeIfAbsent(location.indexOf(":") == -1 ? "base:"+location : location, AssetLocation::compute);
}
public static final AssetLocation tryOf(String location) {
try { return of(location); }
catch(Exception e) {}
return null;
}
private static final AssetLocation compute(String s) {
int index = s.indexOf(":");
return new AssetLocation(s.substring(0, index), s.substring(index + 1));
String domain = s.substring(0, index);
String path = s.substring(index + 1);
if(!isValidDomain(domain)) throw new IllegalArgumentException("Non [a-zA-Z0-9_.-] Character found in domain of location ["+domain+":"+path+"]");
if(!isValidPath(path)) throw new IllegalArgumentException("Non [a-zA-Z0-9/_.-] Character found in path of location ["+domain+":"+path+"]");
return new AssetLocation(domain, path);
}
private static boolean isValidDomain(String s) {
for(int i = 0,m=s.length();i<m;i++) {
if(!isValidDomainChar(s.charAt(i))) return false;
}
return true;
}
private static boolean isValidPath(String s) {
for(int i = 0,m=s.length();i<m;i++) {
if(!isValidPathChar(s.charAt(i))) return false;
}
return true;
}
private static boolean isValidPathChar(char c) {
return c == '/' || isValidDomainChar(c);
}
private static boolean isValidDomainChar(char c) {
return c == '_' || c == '-' || c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
}
}

View File

@ -1,7 +1,11 @@
package speiger.src.coreengine.assets.base;
import java.io.Closeable;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import speiger.src.collections.ints.functions.consumer.IntObjectConsumer;
import speiger.src.collections.objects.collections.ObjectIterable;
@ -37,6 +41,24 @@ public class MultiAsset implements Closeable, ObjectIterable<IAsset> {
return assets.get(index);
}
public <T> Iterable<T> map(AssetMapper<T> mappingFunction, Supplier<T> defaultValue) {
return () -> new Iterator<T>() {
int index = 0;
int size = assets.size();
@Override
public boolean hasNext() { return index < size; }
@Override
public T next() {
if(!hasNext()) throw new NoSuchElementException();
try { return mappingFunction.apply(assets.get(index++)); }
catch(IOException e) {
e.printStackTrace();
return defaultValue.get();
}
}
};
}
@Override
public void forEach(Consumer<? super IAsset> action) {
assets.forEach(action);
@ -56,4 +78,8 @@ public class MultiAsset implements Closeable, ObjectIterable<IAsset> {
public ObjectIterator<IAsset> iterator() {
return assets.iterator();
}
public static interface AssetMapper<T> {
public T apply(IAsset asset) throws IOException;
}
}

View File

@ -28,8 +28,8 @@ public class FolderAssetPackage implements IAssetPackage {
@Override
public void getModifiedTime(String domain, BiConsumer<AssetLocation, FileTime> accept) {
try {
Path start = baseFolder.resolve("assets");
for(Path path : IterableWrapper.wrap(Files.walk(start.resolve(domain)).filter(Files::isRegularFile).iterator())) {
Path start = baseFolder.resolve("assets/").resolve(domain);
for(Path path : IterableWrapper.wrap(Files.walk(start).filter(Files::isRegularFile).iterator())) {
accept.accept(AssetLocation.of(domain, start.relativize(path).toString().replace("\\", "/")), Files.getLastModifiedTime(path));
}
}

View File

@ -1,24 +1,39 @@
package speiger.src.coreengine.rendering.gui.font;
import java.util.Map;
import java.util.function.Consumer;
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.UnbakedGlyth.GlythBaker;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.GlythBaker;
import speiger.src.coreengine.rendering.gui.font.glyth.MissingGlyth;
import speiger.src.coreengine.rendering.tesselation.buffer.IVertexBuilder;
public class Font {
private static final AssetLocation DEFAULT = AssetLocation.of("default");
Map<AssetLocation, FontGroup> fonts;
GlythBaker baker;
float size;
float oversample;
GlythCache[] styledCache = new GlythCache[] {new GlythCache(this, 0), new GlythCache(this, 1), new GlythCache(this, 2), new GlythCache(this, 3)};
MissingGlyth missingGlyth;
protected Font(Map<AssetLocation, FontGroup> fonts, GlythBaker baker, float size, float oversample) {
protected Font(Map<AssetLocation, FontGroup> fonts, GlythBaker baker, float size, float oversample, Consumer<Runnable> clearing) {
this.fonts = fonts;
this.baker = baker;
this.size = size;
this.oversample = oversample;
this.missingGlyth = new MissingGlyth(size, oversample * 2F);
clearing.accept(this::reset);
}
private void reset() {
for(int i = 0;i<4;i++) {
styledCache[i].reset();
}
missingGlyth.cleanCache();
}
public GlythData data(AssetLocation font, int codepoint, int style) {
@ -32,4 +47,39 @@ public class Font {
protected FontGroup font(AssetLocation font) {
return fonts.get(font);
}
public void drawText(String text, float x, float y, int color, TexturedBuffer buffer) {
int previousCodepoint = -1;
for(int i = 0,m=text.length();i<m;) {
int codepoint = text.codePointAt(i);
GlythData data = data(DEFAULT, codepoint, 0);
if(previousCodepoint != -1) {
x += data.kernling(previousCodepoint);
}
Glyth glyth = glyth(DEFAULT, codepoint, 0);
if(glyth.isValid()) {
float minX = glyth.left() + x;
float minY = glyth.top() + y;
float maxX = glyth.right() + x;
float maxY = glyth.bottom() + y;
IVertexBuilder builder = buffer.builderForTexture(glyth.texture());
builder.pos(minX, minY, 0F).tex(glyth.minU(), glyth.maxV()).rgba(-1).endVertex();
builder.pos(maxX, minY, 0F).tex(glyth.maxU(), glyth.maxV()).rgba(-1).endVertex();
builder.pos(maxX, maxY, 0F).tex(glyth.maxU(), glyth.minV()).rgba(-1).endVertex();
builder.pos(maxX, maxY, 0F).tex(glyth.maxU(), glyth.minV()).rgba(-1).endVertex();
builder.pos(minX, maxY, 0F).tex(glyth.minU(), glyth.minV()).rgba(-1).endVertex();
builder.pos(minX, minY, 0F).tex(glyth.minU(), glyth.maxV()).rgba(-1).endVertex();
}
x += data.advance();
i += Character.charCount(codepoint);
previousCodepoint = codepoint;
}
buffer.end();
}
public static interface TexturedBuffer {
public IVertexBuilder builderForTexture(int textureId);
public void end();
}
}

View File

@ -2,49 +2,29 @@ package speiger.src.coreengine.rendering.gui.font;
import java.util.List;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
public class FontGroup {
AssetLocation locations;
List<IFontProvider> providers;
StyledFont[] cache = new StyledFont[] {new StyledFont(0), new StyledFont(1), new StyledFont(2), new StyledFont(3)};
public FontGroup(AssetLocation locations, List<IFontProvider> providers) {
this.locations = locations;
this.providers = providers;
}
public UnbakedGlyth unbaked(int codepoint, int style) {
return cache[style & 0x3].unbaked(codepoint);
public GlythData data(int codepoint, int style, float size, float oversample) {
for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint, style, size, oversample);
if(data != null) return data;
}
return null;
}
public void close() {
providers.forEach(IFontProvider::close);
providers.clear();
}
private class StyledFont {
final int style;
Int2ObjectMap<UnbakedGlyth> dataGlyths = new Int2ObjectOpenHashMap<>();
public StyledFont(int style) {
this.style = style;
}
public UnbakedGlyth unbaked(int codepoint) {
return dataGlyths.computeIfAbsent(codepoint, this::compute);
}
private UnbakedGlyth compute(int codepoint) {
for(int i = 0,m=providers.size();i<m;i++) {
UnbakedGlyth data = providers.get(i).glythData(codepoint, style);
if(data != null) return data;
}
return null;
}
}
}

View File

@ -2,39 +2,82 @@ package speiger.src.coreengine.rendering.gui.font;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import com.google.gson.JsonObject;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.coreengine.assets.AssetFilter;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.base.IAsset;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.assets.base.MultiAsset;
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;
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
import speiger.src.coreengine.rendering.gui.font.providers.STBTrueTypeProvider;
import speiger.src.coreengine.utils.helpers.JsonUtil;
public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, List<IFontProvider>>> {
public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, ObjectList<IFontProvider>>> {
private static final int TEXTURE_SIZE = 2048;
private static final AssetFilter FILTER = AssetFilter.json("font");
Map<AssetLocation, FontGroup> fonts = Object2ObjectMap.builder().linkedMap();
Map<String, BiFunction<JsonObject, IAssetProvider, IFontProvider>> fontParsers = Object2ObjectMap.builder().map();
List<Runnable> listeners = new ObjectArrayList<>();
List<FontTexture> textures = new ObjectArrayList<>();
public FontManager() {
registerParser("stb-ttf", STBTrueTypeProvider::create);
}
public void registerParser(String id, BiFunction<JsonObject, IAssetProvider, IFontProvider> parser) {
fontParsers.putIfAbsent(id, parser);
}
@Override
public String getName() { return null; }
public String getName() { return "Font Manager"; }
@Override
protected Map<AssetLocation, ObjectList<IFontProvider>> prepare(IAssetProvider provider) {
Map<AssetLocation, IFontProvider> loadingCache = Object2ObjectMap.builder().linkedMap();
Object2ObjectMap<AssetLocation, ObjectList<IFontProvider>> providers = Object2ObjectMap.builder().linkedMap();
for(Entry<AssetLocation, MultiAsset> entry : FILTER.multiRoot(provider).entrySet()) {
AssetLocation id = entry.getKey();
for(JsonObject obj : entry.getValue().map(IAsset::json, JsonObject::new)) {
JsonUtil.iterateValues(obj.get("providers"), T -> {
AssetLocation location = AssetLocation.tryOf(T.getAsString());
if(location == null || !location.isInFolder()) return;
IFontProvider font = loadingCache.computeIfAbsent(location.prefix("font"), E -> getFont(E, provider));
if(font == null) return;
providers.supplyIfAbsent(FILTER.id(id), ObjectArrayList::new).add(font);
});
}
}
return providers;
}
@Override
protected void apply(Map<AssetLocation, ObjectList<IFontProvider>> value, IAssetProvider provider) {
reset();
value.forEach((K, V) -> fonts.put(K, new FontGroup(K, V)));
}
@Override
public void destroy() {
reset();
listeners.clear();
}
@Override
protected Map<AssetLocation, List<IFontProvider>> prepare(IAssetProvider provider) {
return null;
}
@Override
protected void apply(Map<AssetLocation, List<IFontProvider>> value, IAssetProvider provider) {
private void reset() {
listeners.forEach(Runnable::run);
textures.forEach(FontTexture::delete);
textures.clear();
fonts.values().forEach(FontGroup::close);
fonts.clear();
}
public Font createFont(float size) {
@ -42,7 +85,7 @@ public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, List<
}
public Font createFont(float size, float oversample) {
return new Font(fonts, this::stitch, size, oversample);
return new Font(fonts, this::stitch, size, oversample, listeners::add);
}
private Glyth stitch(IGlythSheetInfo info) {
@ -55,4 +98,16 @@ public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, List<
Glyth glyth = texture.build(info);
return glyth;
}
private IFontProvider getFont(AssetLocation location, IAssetProvider provider) {
try(IAsset asset = provider.getAsset(location)) {
JsonObject obj = asset.json();
BiFunction<JsonObject, IAssetProvider, IFontProvider> builder = fontParsers.get(obj.get("type").getAsString());
return builder == null ? null : builder.apply(obj, provider);
}
catch(Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,5 +1,7 @@
package speiger.src.coreengine.rendering.gui.font;
import org.lwjgl.system.MemoryUtil;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
@ -43,7 +45,11 @@ public class FontTexture extends BaseTexture {
createTexture();
TextureMetadata metadata = metadataByColor(color);
metadata.applyArguments(textureType());
GLFunctions.upload2DImage(textureType(), 0, metadata, bounds, bounds, 0, 0L);
long allocate = MemoryUtil.nmemAlloc(bounds * bounds * formatByColor(color).components());
ITextureComponentFormat format = formatByColor(color);
GLFunctions.upload2DImage(textureType(), 0, format, bounds, bounds, 0, format, GLDataType.UNSIGNED_BYTE, allocate);
// GLFunctions.upload2DImage(textureType(), 0, metadata, bounds, bounds, 0, allocate);
MemoryUtil.nmemFree(allocate);
}
public Glyth build(IGlythSheetInfo info) {

View File

@ -19,6 +19,10 @@ public class GlythCache {
this.style = style;
}
public void reset() {
cache.clear();
}
private FontCache cache(AssetLocation font) {
return cache.computeIfAbsent(font, FontCache::new);
}
@ -49,12 +53,12 @@ public class GlythCache {
}
private GlythData compute(int codepoint) {
return new GlythData(font.font(fontId).unbaked(codepoint, style), font.size);
GlythData data = font.font(fontId).data(codepoint, style, font.size, font.oversample);
return data == null ? font.missingGlyth : data;
}
private Glyth bake(int codepoint) {
return font.font(fontId).unbaked(codepoint, style).bake(font.baker, font.size, font.oversample);
}
return data(codepoint).bake(font.baker);
}
}
}

View File

@ -10,4 +10,6 @@ public record Glyth(int texture, float minU, float minV, float maxU, float maxV,
public Glyth(int texture, float minU, float minV, float maxU, float maxV) {
this(texture, minU, minV, maxU, maxV, 0, 0, 0, 0);
}
public boolean isValid() { return texture != -1; }
}

View File

@ -1,26 +1,20 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
import speiger.src.collections.ints.functions.function.Int2FloatFunction;
import speiger.src.collections.ints.maps.impl.hash.Int2FloatOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2FloatMap;
public interface GlythData {
public float advance();
public float kernling(int codepoint);
public default float shadowOffset() { return 1F; }
public Glyth bake(GlythBaker baker);
public class GlythData {
float advance;
float shadowOffset;
Int2FloatMap kernlings = new Int2FloatOpenHashMap();
Int2FloatFunction kernling;
public GlythData(float advance, float shadowOffset, Int2FloatFunction kernling) {
this.advance = advance;
this.shadowOffset = shadowOffset;
this.kernling = kernling;
public static record EmptyGlythData(float advance) implements GlythData {
@Override
public Glyth bake(GlythBaker baker) { return Glyth.EMPTY; }
@Override
public float kernling(int codepoint) { return 0F; }
}
public GlythData(UnbakedGlyth glyth, float size) {
this(glyth.advance(size), glyth.shadowOffset(), glyth::kernling);
public static interface GlythBaker {
Glyth bake(IGlythSheetInfo info);
}
public float advance() { return advance; }
public float shadowOffset() { return shadowOffset; }
public float kernling(int codepoint) { return kernlings.computeFloatIfAbsent(codepoint, kernling); }
}

View File

@ -0,0 +1,26 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
import speiger.src.collections.ints.functions.function.Int2FloatFunction;
import speiger.src.collections.ints.maps.impl.hash.Int2FloatOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2FloatMap;
public class GlythDataOld {
float advance;
float shadowOffset;
Int2FloatMap kernlings = new Int2FloatOpenHashMap();
Int2FloatFunction kernling;
public GlythDataOld(float advance, float shadowOffset, Int2FloatFunction kernling) {
this.advance = advance;
this.shadowOffset = shadowOffset;
this.kernling = kernling;
}
public GlythDataOld(GlythData glyth) {
this(glyth.advance(), glyth.shadowOffset(), glyth::kernling);
}
public float advance() { return advance; }
public float shadowOffset() { return shadowOffset; }
public float kernling(int codepoint) { return kernlings.computeFloatIfAbsent(codepoint, kernling); }
}

View File

@ -3,6 +3,15 @@ package speiger.src.coreengine.rendering.gui.font.glyth;
public interface IGlythSheetInfo {
public int width();
public int height();
public default float xOffset() { return 0F; }
public default float yOffset() { return 3F; }
public float oversample();
public boolean isColored();
public void upload(int x, int y);
public default float left() { return xOffset(); }
public default float right() { return xOffset() + width() / oversample(); }
public default float top() { return yOffset(); }
public default float bottom() { return yOffset() + height() / oversample(); }
}

View File

@ -0,0 +1,64 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.rendering.gui.font.FontTexture;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
public class MissingGlyth implements GlythData {
private static final int WIDTH = 10;
private static final int HEIGHT = 57;
float size;
float oversample;
float advance;
float scale;
int width;
int height;
Glyth cached;
public MissingGlyth(float size, float oversample) {
this.size = size;
this.oversample = oversample;
this.scale = (size * oversample) / HEIGHT;
this.advance = WIDTH * (size / HEIGHT);
this.width = (int)(WIDTH * scale);
this.height = (int)(HEIGHT * scale);
}
public void cleanCache() {
cached = null;
}
@Override
public float advance() { return advance; }
@Override
public float kernling(int codepoint) { return 0; }
@Override
public Glyth bake(GlythBaker baker) {
if(cached == null) {
cached = baker.bake(new IGlythSheetInfo() {
@Override
public int width() { return width; }
@Override
public int height() { return height; }
@Override
public void upload(int x, int y) {
Drawable drawable = new Drawable(FontTexture.formatByColor(false), width, height);
draw(drawable);
drawable.upload(x, y, 0, 0, width, height);
drawable.close();
}
@Override
public boolean isColored() { return false; }
@Override
public float oversample() { return oversample; }
});
}
return cached;
}
private void draw(Drawable texture) {
texture.fill(0, 0, width, height, 255);
int offset = MathUtils.ceil(width / 7.5F);
texture.fill(offset, offset, width - offset * 2, height - offset * 2, 0);
}
}

View File

@ -1,22 +0,0 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
public interface UnbakedGlyth {
public float advance(float size);
public float kernling(int codepoint);
public default float shadowOffset() { return 1F; }
public Glyth bake(GlythBaker baker, float size, float oversample);
public static record EmptyGlythData(float advance) implements UnbakedGlyth {
@Override
public Glyth bake(GlythBaker baker, float size, float oversample) { return Glyth.EMPTY; }
@Override
public float advance(float size) { return advance; }
@Override
public float kernling(int codepoint) { return 0F; }
}
public static interface GlythBaker {
Glyth bake(IGlythSheetInfo info);
}
}

View File

@ -1,41 +0,0 @@
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.UnbakedGlyth;
public class BitmapProivder implements IFontProvider {
@Override
public UnbakedGlyth glythData(int codepoint, int style) {
return null;
}
@Override
public void close() {
}
private static class BitmapGlyth implements UnbakedGlyth {
int minX;
int minY;
int maxX;
int maxY;
int glyth;
@Override
public Glyth bake(GlythBaker baker, float size, float oversample) {
return null;
}
@Override
public float advance(float size) {
return 0;
}
@Override
public float kernling(int codepoint) {
return 0;
}
}
}

View File

@ -1,15 +1,14 @@
package speiger.src.coreengine.rendering.gui.font.providers;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth;
import speiger.src.coreengine.rendering.gui.font.glyth.GlythData;
public interface IFontProvider extends AutoCloseable {
public interface IFontProvider {
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 UnbakedGlyth glythData(int codepoint, int style);
@Override
void close();
public GlythData glythData(int codepoint, int style, float size, float oversample);
public void close();
}

View File

@ -18,11 +18,11 @@ import speiger.src.coreengine.assets.base.IAssetProvider;
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.UnbakedGlyth;
import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.EmptyGlythData;
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.rendering.utils.values.textures.GLTextureFormat;
import speiger.src.coreengine.rendering.utils.GLStateTracker;
import speiger.src.coreengine.utils.helpers.JsonUtil;
public class STBTrueTypeProvider implements IFontProvider {
@ -53,7 +53,6 @@ public class STBTrueTypeProvider implements IFontProvider {
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;
@ -74,7 +73,7 @@ public class STBTrueTypeProvider implements IFontProvider {
info.free();
return null;
}
return new TrueTypeInstance(style, MemoryUtil.memAddress(buffer), info, size, oversample, xOff, yOff, shadowOffset, builder.toString());
return new TrueTypeInstance(style, MemoryUtil.memAddress(buffer), info, oversample, xOff, yOff, shadowOffset, builder.toString());
}
catch(Exception e) {
e.printStackTrace();
@ -91,10 +90,9 @@ public class STBTrueTypeProvider implements IFontProvider {
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) {
public TrueTypeInstance(int style, long data, STBTTFontinfo info, float oversample, float xOff, float yOff, float shadowOffset, String skip) {
this.style = style;
this.data = data;
this.info = info;
@ -103,10 +101,9 @@ public class STBTrueTypeProvider implements IFontProvider {
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];
this.ascent = ascent[0];
}
public void free() {
@ -116,10 +113,12 @@ public class STBTrueTypeProvider implements IFontProvider {
data = 0L;
}
public UnbakedGlyth glythData(int codepoint) {
public GlythData glythData(int codepoint, float size, float oversample) {
if(skip.contains(codepoint)) return null;
int glyth = STBTruetype.nstbtt_FindGlyphIndex(info.address(), codepoint);
if(glyth == 0) return null;
oversample *= this.oversample;
float scale = STBTruetype.stbtt_ScaleForPixelHeight(info, size * oversample);
try(MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer left = stack.mallocInt(1);
IntBuffer bottom = stack.mallocInt(1);
@ -128,25 +127,25 @@ public class STBTrueTypeProvider implements IFontProvider {
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);
STBTruetype.stbtt_GetGlyphBitmapBoxSubpixel(info, glyth, scale, scale, 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);
if(maxX - minX <= 0 || maxY - minY <= 0) return new EmptyGlythData(advance.get(0) * scale / oversample);
return new STBGlyth(this, minX, minY, maxX, maxY, advance.get(0), leftSideBearing.get(0), scale, oversample, glyth);
}
catch(Throwable exception) {
exception.printStackTrace();
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
@Override
public UnbakedGlyth glythData(int codepoint, int style) {
public GlythData glythData(int codepoint, int style, float size, float oversample) {
TrueTypeInstance instance = instances[style & 0x3];
return instance == null ? null : instance.glythData(codepoint);
return instance == null ? null : instance.glythData(codepoint, size, oversample);
}
@Override
@ -159,48 +158,56 @@ public class STBTrueTypeProvider implements IFontProvider {
instances = null;
}
private static class STBGlyth implements UnbakedGlyth {
private static class STBGlyth implements GlythData {
final TrueTypeInstance owner;
final float xOffset;
final float yOffset;
final int width;
final int height;
final float oversample;
final float scale;
final float advance;
final int glyth;
public STBGlyth(TrueTypeInstance 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, float scale, float oversample, int glyth) {
this.owner = owner;
this.width = maxX - minX;
this.height = maxY - minY;
this.advance = advance / owner.oversample;
this.scale = scale;
this.oversample = oversample;
this.xOffset = ((leftPadding * scale) + minX + owner.xOff) / oversample;
this.yOffset = ((owner.ascent * scale) - maxY + owner.yOff) / oversample;
this.advance = advance * scale / oversample;
this.glyth = glyth;
}
@Override
public float advance(float size) { return advance; }
public float advance() { return advance; }
@Override
public float shadowOffset() { return owner.shadowOffset; }
@Override
public float kernling(int codepoint) { return STBTruetype.stbtt_GetCodepointKernAdvance(owner.info, codepoint, glyth); }
public float kernling(int codepoint) { return STBTruetype.stbtt_GetCodepointKernAdvance(owner.info, codepoint, glyth) * scale / oversample; }
@Override
public Glyth bake(GlythBaker baker, float size, float oversample) {
public Glyth bake(GlythBaker baker) {
return baker.bake(new IGlythSheetInfo() {
@Override
public float xOffset() { return xOffset; }
@Override
public float yOffset() { return yOffset; }
@Override
public int width() { return width; }
@Override
public int height() { return height; }
@Override
public float oversample() { return oversample; }
@Override
public boolean isColored() { return false; }
@Override
public void upload(int x, int y) {
System.out.println("Testing: "+GLStateTracker.instance().textures.getTextureId(0));
Drawable drawable = new Drawable(FontTexture.formatByColor(false), width, height);
drawable.drawFont(owner.info, glyth, owner.xOff, owner.yOff, 0, 0, width, height, owner.pointScale, owner.pointScale);
Drawable upload = new Drawable(GLTextureFormat.RGBA, width, height);
int pattern = Integer.divideUnsigned(-1, 255);
for(int i = 0,m=width*height;i<m;i++) {
upload.set(i, drawable.getR(i) * pattern);
}
upload.upload(x, y, 0, 0, width, height);
upload.close();
drawable.drawFont(owner.info, glyth, owner.xOff, owner.yOff, 0, 0, width, height, scale, scale);
drawable.upload(x, y, 0, 0, width, height);
drawable.close();
}
});

View File

@ -129,9 +129,18 @@ public class Drawable implements IDrawable, AutoCloseable {
@Override
public void fill(int x, int y, int width, int height, int data) {
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
if(components != 4 && components != 1) throw new IllegalStateException("Format has to be 1 or 4 components");
ensureValid(x, y);
ensureValid(x+width, y+height);
if(components == 1) {
for(int xOff = 0;xOff<width;xOff++) {
for(int yOff = 0;yOff<height;yOff++) {
MemoryUtil.memPutByte(this.pixels + offset(x+xOff, y+yOff), (byte)(data & 0xFF));
}
}
return;
}
for(int xOff = 0;xOff<width;xOff++) {
for(int yOff = 0;yOff<height;yOff++) {
MemoryUtil.memPutInt(this.pixels + offset(x+xOff, y+yOff), data);

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,5 @@
{
"providers": [
"base:roboto/font.json"
]
}

View File

@ -0,0 +1,17 @@
#version 330
in vec4 pass_color;
in vec2 pass_tex;
out vec4 frag_color;
uniform sampler2D texture;
void main()
{
vec4 color = pass_color * texture2D(texture, pass_tex);
if(color.a < 0.2) {
discard;
}
frag_color = color;
}

View File

@ -0,0 +1,17 @@
#version 330
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec2 in_tex;
layout(location = 2) in vec4 in_color;
out vec4 pass_color;
out vec2 pass_tex;
uniform mat4 proViewMatrix;
void main()
{
gl_Position = proViewMatrix * vec4(in_position, 1.0);
pass_color = in_color;
pass_tex = in_tex;
}

View File

@ -9,5 +9,9 @@ uniform sampler2D texture;
void main()
{
frag_color = pass_color * texture2D(texture, pass_tex);
vec4 color = pass_color * texture2D(texture, pass_tex);
if(color.a < 0.2) {
discard;
}
frag_color = color;
}