Progress on the font/Texture system

This commit is contained in:
Speiger 2024-07-13 04:49:32 +02:00
parent 55957d3640
commit e5873759cc
9 changed files with 212 additions and 24 deletions

View File

@ -8,7 +8,6 @@ import javax.imageio.ImageIO;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.lwjgl.stb.STBImage; import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryUtil;
import speiger.src.coreengine.assets.parsers.NativeMemoryParser; import speiger.src.coreengine.assets.parsers.NativeMemoryParser;
import speiger.src.coreengine.math.misc.ColorSpaces; import speiger.src.coreengine.math.misc.ColorSpaces;

View File

@ -4,17 +4,21 @@ import java.util.List;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap; import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap; 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.assets.AssetLocation;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; 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;
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.IFontProvider;
public class FontGroup { public class FontGroup {
private static final int BOLD_FLAG = 1<<31; private static final int BOLD_FLAG = 1<<31;
private static final int TEXTURE_SIZE = 2048;
AssetLocation locations; AssetLocation locations;
List<IFontProvider> providers; List<IFontProvider> providers;
Int2ObjectMap<Glyth> bakedGlyths = new Int2ObjectOpenHashMap<>(); Int2ObjectMap<Glyth> bakedGlyths = new Int2ObjectOpenHashMap<>();
Int2ObjectMap<GlythData> dataGlyths = new Int2ObjectOpenHashMap<>(); Int2ObjectMap<GlythData> dataGlyths = new Int2ObjectOpenHashMap<>();
List<FontTexture> textures = new ObjectArrayList<>();
public FontGroup(AssetLocation locations, List<IFontProvider> providers) { public FontGroup(AssetLocation locations, List<IFontProvider> providers) {
this.locations = locations; this.locations = locations;
@ -42,8 +46,19 @@ public class FontGroup {
codepoint &= ~BOLD_FLAG; codepoint &= ~BOLD_FLAG;
for(int i = 0,m=providers.size();i<m;i++) { for(int i = 0,m=providers.size();i<m;i++) {
GlythData data = providers.get(i).glythData(codepoint); GlythData data = providers.get(i).glythData(codepoint);
if(data != null) return data.bake(bold); if(data != null) return data.bake(bold, this::stitch);
} }
return null; return null;
} }
private Glyth stitch(IGlythSheetInfo info, boolean bold) {
for(int i = 0,m=textures.size();i<m;i++) {
Glyth glyth = textures.get(i).build(info, bold);
if(glyth != null) return glyth;
}
FontTexture texture = new FontTexture(TEXTURE_SIZE);
textures.add(texture);
Glyth glyth = texture.build(info, bold);
return glyth;
}
} }

View File

@ -0,0 +1,107 @@
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.utils.values.textures.GLTextureFormat;
public class FontTexture extends BaseTexture {
int bounds;
Slot slot;
public FontTexture(int bounds) {
this.bounds = bounds;
slot = new Slot(0, 0, bounds, bounds);
}
@Override
public void load(IAssetProvider provider) {
GL45.glTextureStorage2D(id, 1, GLTextureFormat.RGBA.glValue(), bounds, bounds);
}
public Glyth build(IGlythSheetInfo info, boolean bold) {
Slot result = slot.insert(info);
if(result != null) {
info.upload(id(), 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 null;
}
@Override
public int width() {
return bounds;
}
@Override
public int height() {
return bounds;
}
public static class Slot {
final int x;
final int y;
final int width;
final int height;
Slot[] children = null;
boolean occupied;
public Slot(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public Slot insert(IGlythSheetInfo info) {
if(children == null) {
if(occupied) return null;
int iw = info.width();
int ih = info.height();
if(iw > width || ih > height) return null;
if(iw == width && ih == height) {
occupied = true;
return this;
}
int dw = width - iw;
int dh = height - ih;
children = new Slot[dw > 0 && dh > 0 ? 3 : 2];
children[0] = new Slot(x, y, iw, ih);
expandSlot(iw, ih, dw, dh);
return children[0].insert(info);
}
for(int i = 0,m=children.length;i<m;i++) {
Slot slot = children[i].insert(info);
if(slot != null) return slot;
}
return null;
}
private void expandSlot(int rw, int rh, int dw, int dh) {
if(dw > 0 && dh > 0) {
if(dw >= dh) {
children[1] = new Slot(x + rw, y, dw, rh);
children[2] = new Slot(x, y + rh, width, dh);
}
else {
children[1] = new Slot(x, y + rh, rw, dh);
children[2] = new Slot(x + rw, y, dw, height);
}
}
else if(dw == 0) children[1] = new Slot(x, y + rh, rw, dh);
else if(dh == 0) children[1] = new Slot(x + rw, y, dw, rh);
}
@Override
public String toString() {
return "Slot[x="+x+", y="+y+", w="+width+", h="+height+"]";
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
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);
}

View File

@ -1,24 +1,29 @@
package speiger.src.coreengine.rendering.textures.custom; package speiger.src.coreengine.rendering.textures.custom;
import org.lwjgl.opengl.GL46;
import org.lwjgl.stb.STBImage; import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import speiger.src.coreengine.math.misc.ColorSpaces; import speiger.src.coreengine.math.misc.ColorSpaces;
import speiger.src.coreengine.rendering.utils.values.IGLValue.ITextureComponentFormat;
public class Drawable implements IDrawable, AutoCloseable { public class Drawable implements IDrawable, AutoCloseable {
ITextureComponentFormat format;
int width; int width;
int height; int height;
long components;
long pixels; long pixels;
boolean isSTB; boolean isSTB;
public Drawable(int width, int height) { public Drawable(ITextureComponentFormat format, int width, int height) {
this(width, height, MemoryUtil.nmemAllocChecked(4L * width * height), false); this(format, width, height, MemoryUtil.nmemAllocChecked(format.components() * width * height), false);
} }
public Drawable(int width, int height, long pixels, boolean isSTB) { public Drawable(ITextureComponentFormat format, int width, int height, long pixels, boolean isSTB) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.pixels = pixels; this.pixels = pixels;
this.components = format.components();
this.isSTB = isSTB; this.isSTB = isSTB;
} }
@ -30,6 +35,7 @@ public class Drawable implements IDrawable, AutoCloseable {
public int height() { return height; } public int height() { return height; }
public long pixels() { return pixels; } public long pixels() { return pixels; }
public boolean isSTBImage() { return isSTB; } public boolean isSTBImage() { return isSTB; }
public ITextureComponentFormat format() { return format; }
@Override @Override
public void close() { public void close() {
@ -40,7 +46,7 @@ public class Drawable implements IDrawable, AutoCloseable {
} }
protected long offset(int x, int y) { protected long offset(int x, int y) {
return ((y * width()) + x) << 2; return ((y * width()) + x) * components;
} }
protected void ensureValid(int index) { protected void ensureValid(int index) {
@ -53,38 +59,48 @@ public class Drawable implements IDrawable, AutoCloseable {
if(pixels == 0L) throw new IllegalStateException("Pixel Data doesn't exist"); if(pixels == 0L) throw new IllegalStateException("Pixel Data doesn't exist");
} }
public void upload(int texture, int level, int x, int y, int texX, int texY, int width, int height) {
}
@Override @Override
public void set(int index, int data) { public void set(int index, int data) {
ensureValid(index); ensureValid(index);
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
MemoryUtil.memPutInt(index * 4L, data); MemoryUtil.memPutInt(index * 4L, data);
} }
@Override @Override
public void setR(int index, int red) { public void setR(int index, int red) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index); ensureValid(index);
MemoryUtil.memPutByte(index * 4L, (byte)(red & 0xFF)); MemoryUtil.memPutByte(index * components + format.redOffset(), (byte)(red & 0xFF));
} }
@Override @Override
public void setG(int index, int green) { public void setG(int index, int green) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index); ensureValid(index);
MemoryUtil.memPutByte(index * 4L + 1L, (byte)(green & 0xFF)); MemoryUtil.memPutByte(index * components + format.greenOffset(), (byte)(green & 0xFF));
} }
@Override @Override
public void setB(int index, int blue) { public void setB(int index, int blue) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index); ensureValid(index);
MemoryUtil.memPutByte(index * 4L + 2L, (byte)(blue & 0xFF)); MemoryUtil.memPutByte(index * components + format.blueOffset(), (byte)(blue & 0xFF));
} }
@Override @Override
public void setA(int index, int alpha) { public void setA(int index, int alpha) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index); ensureValid(index);
MemoryUtil.memPutByte(index * 4L + 3L, (byte)(alpha & 0xFF)); MemoryUtil.memPutByte(index * components + format.alphaOffset(), (byte)(alpha & 0xFF));
} }
@Override @Override
public void fill(int x, int y, int width, int height, int data) { 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");
ensureValid(x, y); ensureValid(x, y);
ensureValid(x+width, y+height); ensureValid(x+width, y+height);
for(int yOff = 0;yOff<height;yOff++) { for(int yOff = 0;yOff<height;yOff++) {
@ -94,30 +110,35 @@ public class Drawable implements IDrawable, AutoCloseable {
@Override @Override
public int get(int index) { public int get(int index) {
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
ensureValid(index); ensureValid(index);
return MemoryUtil.memGetInt(index * 4L); return MemoryUtil.memGetInt(index * 4L);
} }
@Override @Override
public int getR(int index) { public int getR(int index) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index); ensureValid(index);
return MemoryUtil.memGetByte(index * 4L); return MemoryUtil.memGetByte(index * 4L);
} }
@Override @Override
public int getG(int index) { public int getG(int index) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index); ensureValid(index);
return MemoryUtil.memGetByte(index * 4L + 1L); return MemoryUtil.memGetByte(index * 4L + 1L);
} }
@Override @Override
public int getB(int index) { public int getB(int index) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index); ensureValid(index);
return MemoryUtil.memGetByte(index * 4L + 2L); return MemoryUtil.memGetByte(index * 4L + 2L);
} }
@Override @Override
public int getA(int index) { public int getA(int index) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index); ensureValid(index);
return MemoryUtil.memGetByte(index * 4L + 3L); return MemoryUtil.memGetByte(index * 4L + 3L);
} }

View File

@ -30,7 +30,7 @@ public class SimpleTexture extends BaseTexture {
@Override @Override
public void load(IAssetProvider provider) { public void load(IAssetProvider provider) {
long address = 0L; long address = 0L;
int channel = GLTextureFormat.toComponents(metadata.getExternalFormat()); int channel = GLTextureFormat.stbComponents(metadata.getExternalFormat());
try(IAsset asset = provider.getAsset(location)) { try(IAsset asset = provider.getAsset(location)) {
ByteBuffer buffer = asset.custom(NativeMemoryParser.INSTANCE); ByteBuffer buffer = asset.custom(NativeMemoryParser.INSTANCE);
int[] width = new int[1]; int[] width = new int[1];

View File

@ -18,7 +18,20 @@ public interface IGLValue {
public static interface IShaderType extends IGLValue {}; public static interface IShaderType extends IGLValue {};
public static interface ITextureFormatType extends IGLValue { public static interface ITextureFormatType extends IGLValue {
public boolean internal(); public boolean internal();
public int components();
public int componentMask();
} }
public static interface ITextureComponentFormat extends ITextureFormatType {
public default boolean hasRed() { return redOffset() != -1; }
public default boolean hasGreen() { return greenOffset() != -1; }
public default boolean hasBlue() { return blueOffset() != -1; }
public default boolean hasAlpha() { return alphaOffset() != -1; }
public int redOffset();
public int greenOffset();
public int blueOffset();
public int alphaOffset();
}
public static interface ITextureParameter extends IGLValue { public static interface ITextureParameter extends IGLValue {
public boolean isValid(int value); public boolean isValid(int value);
} }

View File

@ -3,23 +3,36 @@ package speiger.src.coreengine.rendering.utils.values.textures;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import speiger.src.coreengine.rendering.utils.values.IGLValue.ITextureFormatType; import speiger.src.coreengine.rendering.utils.values.IGLValue.ITextureComponentFormat;
public enum GLTextureFormat implements ITextureFormatType { public enum GLTextureFormat implements ITextureComponentFormat {
R(GL11.GL_RED, true), R(GL11.GL_RED, 1, 0, -1, -1, -1, true),
RG(GL30.GL_RG, true), RG(GL30.GL_RG, 2, 0, 1, -1, -1, true),
RGB(GL11.GL_RGB, true), RGB(GL11.GL_RGB, 3, 0, 1, 2, -1, true),
RGBA(GL11.GL_RGBA, true), RGBA(GL11.GL_RGBA, 4, 0, 1, 2, 3, true),
DEPTH(GL11.GL_DEPTH_COMPONENT, true), DEPTH(GL11.GL_DEPTH_COMPONENT, 1, 0, -1, -1, -1, true),
DEPTH_STENCIL(GL30.GL_DEPTH_STENCIL, true), DEPTH_STENCIL(GL30.GL_DEPTH_STENCIL, 2, 0, 1, -1, -1, true),
LUMINANCE(GL11.GL_LUMINANCE, false), LUMINANCE(GL11.GL_LUMINANCE, 1, 0, 0, 0, 0, false),
LUMINANCE_ALPHA(GL11.GL_LUMINANCE_ALPHA, false); LUMINANCE_ALPHA(GL11.GL_LUMINANCE_ALPHA, 2, 0, -1, -1, 1, false);
int glMode; int glMode;
boolean internal; boolean internal;
private GLTextureFormat(int glMode, boolean internal) { int components;
int componentMask;
int redOffset;
int greenOffset;
int blueOffset;
int alphaOffset;
private GLTextureFormat(int glMode, int components, int redOffset, int greenOffset, int blueOffset, int alphaOffset, boolean internal) {
this.glMode = glMode; this.glMode = glMode;
this.components = components;
this.componentMask = (1 << (components * 8)) - 1;
this.redOffset = redOffset;
this.greenOffset = greenOffset;
this.blueOffset = blueOffset;
this.alphaOffset = alphaOffset;
this.internal = internal; this.internal = internal;
} }
@ -27,8 +40,17 @@ public enum GLTextureFormat implements ITextureFormatType {
public int glValue() { return glMode; } public int glValue() { return glMode; }
@Override @Override
public boolean internal() { return internal; } public boolean internal() { return internal; }
@Override
public int components() { return stbComponents(glMode); }
@Override
public int componentMask() { return componentMask; }
public static int toComponents(int glValue) { public int redOffset() { return redOffset; }
public int greenOffset() { return greenOffset; }
public int blueOffset() { return blueOffset; }
public int alphaOffset() { return alphaOffset; }
public static int stbComponents(int glValue) {
return switch(glValue) { return switch(glValue) {
case GL11.GL_LUMINANCE -> 1; case GL11.GL_LUMINANCE -> 1;
case GL11.GL_LUMINANCE_ALPHA -> 2; case GL11.GL_LUMINANCE_ALPHA -> 2;