Finishing font prototype!

This commit is contained in:
Speiger 2024-07-24 00:19:36 +02:00
parent 7f8b3e8786
commit 0139086858
13 changed files with 211 additions and 59 deletions

View File

@ -10,6 +10,11 @@ import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.AssetManager;
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.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.input.devices.FileDrop;
import speiger.src.coreengine.rendering.input.devices.Joystick;
import speiger.src.coreengine.rendering.input.devices.Keyboard;
@ -61,12 +66,14 @@ public class NewInputTest {
System.out.println("Testing: "+GL.getCapabilities().OpenGL46);
DynamicTexture texture = new DynamicTexture(16, 16, DynamicTexture.DEFAULT_PARAMETERS);
texture.fill(0, 0, 16, 16, -1);
texture.fill(0, 0, 8, 8, 255, 0, 0, 255);
texture.fill(8, 0, 8, 8, 0, 255, 0, 255);
texture.fill(8, 8, 8, 8, 255, 0, 0, 255);
texture.fill(0, 8, 8, 8, 0, 0, 255, 255);
IFontProvider provider = STBTrueTypeProvider.create(AssetLocation.of("font/Roboto-Medium.ttf"), assets);
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);
texture.process(true);
window.visible(true);
@ -74,11 +81,24 @@ public class NewInputTest {
if(T.key() == GLFW.GLFW_KEY_T) {
T.cancel();
texture.bind();
Drawable drawable = new Drawable(GLTextureFormat.RGBA, 8, 8);
drawable.fill(0, 0, 8, 8, 255, 255, 0, 255);
drawable.upload(6, 6, 2, 2, 4, 4);
Drawable drawable = new Drawable(GLTextureFormat.RGBA, 256, 256);
drawable.fill(0, 0, 256, 256, 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() {
@Override
public Glyth bake(IGlythSheetInfo info) {
int width = info.width();
int height = info.height();
info.upload(256 - (width >> 2), 256 - (height >> 2));
return null;
}
});
}
});
VertexBuilder builder = new VertexBuilder(255);

View File

@ -25,8 +25,8 @@ public class FontGroup {
this.providers = providers;
}
public GlythData data(int codepoint) {
return dataGlyths.computeIfAbsent(codepoint, this::compute);
public GlythData data(int codepoint, boolean bold) {
return dataGlyths.computeIfAbsent(codepoint | (bold ? 0 : BOLD_FLAG), this::compute);
}
public Glyth glyth(int codepoint, boolean bold) {
@ -34,8 +34,10 @@ public class FontGroup {
}
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);
GlythData data = providers.get(i).glythData(codepoint, bold);
if(data != null) return data;
}
return null;
@ -45,20 +47,20 @@ public class FontGroup {
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);
if(data != null) return data.bake(bold, this::stitch);
GlythData data = providers.get(i).glythData(codepoint, bold);
if(data != null) return data.bake(this::stitch);
}
return null;
}
private Glyth stitch(IGlythSheetInfo info, boolean bold) {
private Glyth stitch(IGlythSheetInfo info) {
for(int i = 0,m=textures.size();i<m;i++) {
Glyth glyth = textures.get(i).build(info, bold);
Glyth glyth = textures.get(i).build(info);
if(glyth != null) return glyth;
}
FontTexture texture = new FontTexture(TEXTURE_SIZE);
FontTexture texture = new FontTexture(TEXTURE_SIZE, info.isColored());
textures.add(texture);
Glyth glyth = texture.build(info, bold);
Glyth glyth = texture.build(info);
return glyth;
}
}

View File

@ -1,50 +1,71 @@
package speiger.src.coreengine.rendering.gui.font;
import org.lwjgl.opengl.GL45;
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;
import speiger.src.coreengine.rendering.textures.base.BaseTexture;
import speiger.src.coreengine.rendering.textures.base.TextureMetadata;
import speiger.src.coreengine.rendering.utils.GLFunctions;
import speiger.src.coreengine.rendering.utils.values.GLDataType;
import speiger.src.coreengine.rendering.utils.values.IGLValue.ITextureComponentFormat;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureFormat;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureParameter;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureValue;
public class FontTexture extends BaseTexture {
private static final TextureMetadata COLOR = TextureMetadata.builder()
.externalFormat(GLTextureFormat.RGBA).internalFormat(GLTextureFormat.RGBA).dataFormat(GLDataType.UNSIGNED_BYTE).mipmapping(false)
.arguement(GLTextureParameter.MIN_FILTER, GLTextureValue.NEAREST).arguement(GLTextureParameter.MAG_FILTER, GLTextureValue.NEAREST)
.arguement(GLTextureParameter.WRAP_S, GLTextureValue.CLAMP_TO_EDGE).arguement(GLTextureParameter.WRAP_T, GLTextureValue.CLAMP_TO_EDGE).build();
private static final TextureMetadata NO_COLOR = COLOR.copy().internalFormat(GLTextureFormat.LUMINANCE).externalFormat(GLTextureFormat.LUMINANCE).arguement(GLTextureParameter.SWIZZLE_RGBA, GLTextureValue.RED).build();
boolean color;
int bounds;
Slot slot;
public FontTexture(int bounds) {
public FontTexture(int bounds, boolean color) {
this.bounds = bounds;
this.color = color;
slot = new Slot(0, 0, bounds, bounds);
track();
}
public static ITextureComponentFormat formatByColor(boolean colored) {
return colored ? GLTextureFormat.RGBA : GLTextureFormat.LUMINANCE;
}
public static TextureMetadata metadataByColor(boolean colored) {
return colored ? COLOR : NO_COLOR;
}
@Override
public void load(IAssetProvider provider) {
//TODO use old texture system instead
GL45.glTextureStorage2D(id, 1, GLTextureFormat.RGBA.glValue(), bounds, bounds);
createTexture();
TextureMetadata metadata = metadataByColor(color);
metadata.applyArguments(textureType());
GLFunctions.upload2DImage(textureType(), 0, metadata, bounds, bounds, 0, 0L);
}
public Glyth build(IGlythSheetInfo info, boolean bold) {
public Glyth build(IGlythSheetInfo info) {
if(color != info.isColored()) return null;
Slot result = slot.insert(info);
if(result != null) {
info.upload(id(), result.x, result.y);
bind();
info.upload(result.x, result.y);
float minU = (slot.x + 0.01F) / bounds;
float maxU = (slot.x - 0.01F + info.width()) / bounds;
float minV = (slot.y + 0.01F) / bounds;
float maxV = (slot.y - 0.01F + info.height()) / bounds;
return new Glyth(id(), bold, minU, minV, maxU, maxV);
return new Glyth(id(), minU, minV, maxU, maxV);
}
return null;
}
public boolean colored() { return color; }
@Override
public int width() {
return bounds;
}
public int width() { return bounds; }
@Override
public int height() {
return bounds;
}
public int height() { return bounds; }
public static class Slot {
final int x;

View File

@ -1,3 +1,3 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
public record Glyth(int texture, boolean bold, float minU, float minV, float maxU, float maxV) {}
public record Glyth(int texture, float minU, float minV, float maxU, float maxV) {}

View File

@ -1,10 +1,10 @@
package speiger.src.coreengine.rendering.gui.font.glyth;
public interface GlythData {
public float advance(boolean bold);
public Glyth bake(boolean bold, GlythBaker baker);
public float advance();
public Glyth bake(GlythBaker baker);
public static interface GlythBaker {
Glyth bake(IGlythSheetInfo info, boolean bold);
Glyth bake(IGlythSheetInfo info);
}
}

View File

@ -3,6 +3,6 @@ package speiger.src.coreengine.rendering.gui.font.glyth;
public interface IGlythSheetInfo {
public int width();
public int height();
public int upload(int texture, int x, int y);
public boolean isColored();
public void upload(int x, int y);
}

View File

@ -3,7 +3,7 @@ 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);
public GlythData glythData(int codepoint, boolean bold);
@Override
void close();
}

View File

@ -1,15 +1,29 @@
package speiger.src.coreengine.rendering.gui.font.providers;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTruetype;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import speiger.src.collections.ints.sets.IntOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
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.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.IGlythSheetInfo;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
public class STBTrueTypeProvider implements IFontProvider {
long data;
STBTTFontinfo info;
IntSet skip;
IntSet skip = new IntOpenHashSet();
float oversample;
float xOff;
float yOff;
@ -29,14 +43,97 @@ public class STBTrueTypeProvider implements IFontProvider {
this.ascent = pointScale * ascent[0];
}
@Override
public GlythData glythData(int codepoint) {
public static IFontProvider create(AssetLocation location, IAssetProvider provider) {
try(IAsset asset = provider.getAsset(location)) {
ByteBuffer buffer = asset.custom(NativeMemoryParser.INSTANCE);
STBTTFontinfo info = STBTTFontinfo.create();
if(!STBTruetype.stbtt_InitFont(info, buffer)) {
System.out.println("Couldn't load font");
MemoryUtil.memFree(buffer);
info.free();
return null;
}
return new STBTrueTypeProvider(MemoryUtil.memAddress(buffer), info, 10.2F, 24F, 0, 0, "");
}
catch(Exception e) {
e.printStackTrace();
}
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);
}
}
@Override
public void close() {
if(data == 0L) return;
info.free();
MemoryUtil.nmemFree(data);
data = 0L;
}
private static class STBGlyth implements GlythData {
final STBTrueTypeProvider 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) {
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;
}
@Override
public Glyth bake(GlythBaker baker) {
return baker.bake(new IGlythSheetInfo() {
@Override
public int width() { return width; }
@Override
public int height() { return height; }
@Override
public boolean isColored() { return false; }
@Override
public void upload(int x, int y) {
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(x, y, 0, 0, width, height);
drawable.close();
}
});
}
}
}

View File

@ -1,5 +1,7 @@
package speiger.src.coreengine.rendering.textures.base;
import java.util.function.IntConsumer;
import speiger.src.collections.longs.lists.LongArrayList;
import speiger.src.collections.longs.lists.LongList;
import speiger.src.coreengine.math.BitUtil;
@ -16,6 +18,7 @@ public class TextureMetadata {
boolean isSTBData = true;
boolean mipmapping = true;
LongList arguments = new LongArrayList();
IntConsumer specialarguments;
private TextureMetadata() {}
@ -24,6 +27,9 @@ public class TextureMetadata {
long value = arguments.getLong(i);
GLFunctions.textureParameter(textureType.glValue(), BitUtil.intKey(value), BitUtil.intValue(value));
}
if(specialarguments != null) {
specialarguments.accept(textureType.glValue());
}
}
public LongList getArguments() {
@ -50,6 +56,7 @@ public class TextureMetadata {
metadata.isSTBData = data.isSTBData;
metadata.mipmapping = data.mipmapping;
metadata.arguments.addAll(data.arguments);
metadata.specialarguments = data.specialarguments;
}
public Builder internalFormat(ITextureFormatType type) {
@ -119,6 +126,11 @@ public class TextureMetadata {
return this;
}
public Builder specialarguments(IntConsumer command) {
metadata.specialarguments = command;
return this;
}
public TextureMetadata build() {
TextureMetadata meta = metadata;
metadata = null;

View File

@ -66,7 +66,7 @@ public class Drawable implements IDrawable, AutoCloseable {
if(pixels == 0L) throw new IllegalStateException("Pixel Data doesn't exist");
}
public void drawFont(STBTTFontinfo info, int glyth, int sourceX, int sourceY, int targetX, int targetY, int width, int height, float scaleX, float scaleY) {
public void drawFont(STBTTFontinfo info, int glyth, float sourceX, float sourceY, int targetX, int targetY, int width, int height, float scaleX, float scaleY) {
ensureValid(targetX, targetY);
ensureValid(targetX + width, targetY + height);
if(components != 1) throw new IllegalStateException("Format has to be 1 component");
@ -96,35 +96,35 @@ public class Drawable implements IDrawable, AutoCloseable {
public void set(int index, int data) {
ensureValid(index);
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
MemoryUtil.memPutInt(index * 4L, data);
MemoryUtil.memPutInt(this.pixels + index * 4L, data);
}
@Override
public void setR(int index, int red) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index);
MemoryUtil.memPutByte(index * components + format.redOffset(), (byte)(red & 0xFF));
MemoryUtil.memPutByte(this.pixels + index * components + format.redOffset(), (byte)(red & 0xFF));
}
@Override
public void setG(int index, int green) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index);
MemoryUtil.memPutByte(index * components + format.greenOffset(), (byte)(green & 0xFF));
MemoryUtil.memPutByte(this.pixels + index * components + format.greenOffset(), (byte)(green & 0xFF));
}
@Override
public void setB(int index, int blue) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index);
MemoryUtil.memPutByte(index * components + format.blueOffset(), (byte)(blue & 0xFF));
MemoryUtil.memPutByte(this.pixels + index * components + format.blueOffset(), (byte)(blue & 0xFF));
}
@Override
public void setA(int index, int alpha) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index);
MemoryUtil.memPutByte(index * components + format.alphaOffset(), (byte)(alpha & 0xFF));
MemoryUtil.memPutByte(this.pixels + index * components + format.alphaOffset(), (byte)(alpha & 0xFF));
}
@Override
@ -143,34 +143,34 @@ public class Drawable implements IDrawable, AutoCloseable {
public int get(int index) {
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
ensureValid(index);
return MemoryUtil.memGetInt(index * 4L);
return MemoryUtil.memGetInt(this.pixels + index * 4L);
}
@Override
public int getR(int index) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index);
return MemoryUtil.memGetByte(index * components + format.redOffset());
return MemoryUtil.memGetByte(this.pixels + index * components + format.redOffset());
}
@Override
public int getG(int index) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index);
return MemoryUtil.memGetByte(index * components + format.greenOffset());
return MemoryUtil.memGetByte(this.pixels + index * components + format.greenOffset());
}
@Override
public int getB(int index) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index);
return MemoryUtil.memGetByte(index * components + format.blueOffset());
return MemoryUtil.memGetByte(this.pixels + index * components + format.blueOffset());
}
@Override
public int getA(int index) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index);
return MemoryUtil.memGetByte(index * components + format.alphaOffset());
return MemoryUtil.memGetByte(this.pixels + index * components + format.alphaOffset());
}
}

View File

@ -12,8 +12,8 @@ public enum GLTextureFormat implements ITextureComponentFormat {
RGBA(GL11.GL_RGBA, 4, 0, 1, 2, 3, true),
DEPTH(GL11.GL_DEPTH_COMPONENT, 1, 0, -1, -1, -1, true),
DEPTH_STENCIL(GL30.GL_DEPTH_STENCIL, 2, 0, 1, -1, -1, true),
LUMINANCE(GL11.GL_LUMINANCE, 1, 0, 0, 0, 0, false),
LUMINANCE_ALPHA(GL11.GL_LUMINANCE_ALPHA, 2, 0, -1, -1, 1, false);
LUMINANCE(GL11.GL_LUMINANCE, 1, 0, 0, 0, 0, true),
LUMINANCE_ALPHA(GL11.GL_LUMINANCE_ALPHA, 2, 0, -1, -1, 1, true);
int glMode;
boolean internal;

View File

@ -29,7 +29,7 @@ public enum GLTextureParameter implements ITextureParameter {
SWIZZLE_G(GL33.GL_TEXTURE_SWIZZLE_G, GLTextureValue.RED, GLTextureValue.GREEN, GLTextureValue.BLUE, GLTextureValue.ALPHA, GLBlendFactor.ZERO, GLBlendFactor.ONE),
SWIZZLE_B(GL33.GL_TEXTURE_SWIZZLE_B, GLTextureValue.RED, GLTextureValue.GREEN, GLTextureValue.BLUE, GLTextureValue.ALPHA, GLBlendFactor.ZERO, GLBlendFactor.ONE),
SWIZZLE_A(GL33.GL_TEXTURE_SWIZZLE_A, GLTextureValue.RED, GLTextureValue.GREEN, GLTextureValue.BLUE, GLTextureValue.ALPHA, GLBlendFactor.ZERO, GLBlendFactor.ONE),
SWIZZLE_RGBA(GL33.GL_TEXTURE_SWIZZLE_RGBA, false),
SWIZZLE_RGBA(GL33.GL_TEXTURE_SWIZZLE_RGBA, GLTextureValue.RED, GLTextureValue.GREEN, GLTextureValue.BLUE, GLTextureValue.ALPHA, GLBlendFactor.ZERO, GLBlendFactor.ONE),
WRAP_S(GL11.GL_TEXTURE_WRAP_S, GLTextureValue.CLAMP_TO_EDGE, GLTextureValue.CLAMP_TO_BORDER, GLTextureValue.MIRRORED_REPEAT, GLTextureValue.REPEAT, GLTextureValue.MIRROR_CLAMP_TO_EDGE),
WRAP_T(GL11.GL_TEXTURE_WRAP_T, GLTextureValue.CLAMP_TO_EDGE, GLTextureValue.CLAMP_TO_BORDER, GLTextureValue.MIRRORED_REPEAT, GLTextureValue.REPEAT, GLTextureValue.MIRROR_CLAMP_TO_EDGE),
WRAP_R(GL12.GL_TEXTURE_WRAP_R, GLTextureValue.CLAMP_TO_EDGE, GLTextureValue.CLAMP_TO_BORDER, GLTextureValue.MIRRORED_REPEAT, GLTextureValue.REPEAT, GLTextureValue.MIRROR_CLAMP_TO_EDGE);

Binary file not shown.