SDF font rendering with proper kerning
This commit is contained in:
parent
6772912f66
commit
12ad72636b
20
README.md
20
README.md
|
@ -24,7 +24,6 @@ version numbers.*
|
|||
### Safe submodules
|
||||
* **PlutoCore** - Stable
|
||||
* **PlutoFramebuffer** - Stable
|
||||
* **PlutoGUI** - Stable, awaiting a rewrite
|
||||
* **PlutoMesher** - Stable
|
||||
* **PlutoShader** - Stable
|
||||
* **PlutoTexture** - Stable
|
||||
|
@ -34,6 +33,7 @@ version numbers.*
|
|||
* **PlutoLib** - Mostly stable
|
||||
|
||||
### Unstable submodules
|
||||
* **PlutoGUI** - Recently rewritten, the API is highly unstable
|
||||
* **PlutoRuntime** - Somewhat tentative, the module API has been rewritten and might contain bugs
|
||||
* **PlutoAudio** - Somewhat usable, unfinished
|
||||
|
||||
|
@ -42,17 +42,25 @@ version numbers.*
|
|||
|
||||
See `NEXT_RELEASE_DRAFT.md` for details.
|
||||
|
||||
### To be fixed
|
||||
[ *Features or bugs that should be fixed **ASAP*** ]
|
||||
* Implement gradient variation support for Libra fills
|
||||
* Finish gradient rendering in FragmentFontShader.glsl
|
||||
* Improve code quality in PlutoGUI
|
||||
|
||||
### Very high priority
|
||||
[ *Implemented in the current release.* ]
|
||||
* Streamline PlutoLib, remove bad APIs and improve code quality
|
||||
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
||||
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
||||
* The stage system and automated asset loading
|
||||
* Rewrite PlutoGUI
|
||||
|
||||
### High priority
|
||||
[ *Implemented in the next release.* ]
|
||||
* Finish PlutoAudio
|
||||
* Depends on the stage system
|
||||
* Expand upon the Color API
|
||||
* Color mixing and blending
|
||||
* Color transformation
|
||||
* High-performance serialization
|
||||
|
||||
### Normal priority
|
||||
[ *Planned for an upcoming release.* ]
|
||||
|
@ -68,7 +76,3 @@ See `NEXT_RELEASE_DRAFT.md` for details.
|
|||
* This feature requires a full rewrite and possibly a complete overhaul
|
||||
* Mods should have limited execution levels, for example restricted file access
|
||||
or disabled native library loading (this is probably not possible)
|
||||
* Expand upon the Color API
|
||||
* Color mixing and blending
|
||||
* Color transformation
|
||||
* High-performance serialization
|
|
@ -12,6 +12,7 @@
|
|||
* `[PlutoRuntime]` Fixed opening .zip filesystems
|
||||
* `[PlutoShader]` Added `UniformArrayFloat`, `UniformArrayRGBA`,
|
||||
`UniformArrayVec2`, `UniformArrayVec3`, `UniformArrayVec4`
|
||||
* `[PlutoRuntime]` `SmartSeverity.MODULE_CHECK` now correctly uses the standard output instead of `stderr`.
|
||||
|
||||
## 22.0.0.0-alpha.7
|
||||
* `[PlutoRuntime]` Fixed several resource filesystem bugs
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
package org.plutoengine.graphics;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.primitives.Rectanglef;
|
||||
import org.plutoengine.graphics.gui.PlutoGUICommandParser;
|
||||
import org.plutoengine.graphics.gui.stbttf.STBTTBasicTextShaper;
|
||||
import org.plutoengine.graphics.gui.stbttf.STBTTFont;
|
||||
import org.plutoengine.libra.command.LiCommandBuffer;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
|
||||
import org.plutoengine.libra.text.LiTextInfo;
|
||||
import org.plutoengine.libra.text.font.LiFontFamily;
|
||||
import org.plutoengine.libra.text.shaping.TextShaper;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class ImmediateFontRenderer
|
||||
{
|
||||
public static void drawString(float x, float y, String text, LiFontFamily<STBTTFont> font, TextStyleOptions style)
|
||||
{
|
||||
var shaper = new STBTTBasicTextShaper();
|
||||
var info = shaper.shape(EnumSet.of(TextShaper.EnumFeature.KERNING), font, text, style);
|
||||
|
||||
draw(x, y, info, style);
|
||||
}
|
||||
|
||||
public static void drawStringNoKern(float x, float y, String text, LiFontFamily<STBTTFont> font, TextStyleOptions style)
|
||||
{
|
||||
var shaper = new STBTTBasicTextShaper();
|
||||
var info = shaper.shape(EnumSet.noneOf(TextShaper.EnumFeature.class), font, text, style);
|
||||
|
||||
draw(x, y, info, style);
|
||||
}
|
||||
|
||||
public static void draw(float x, float y, LiTextInfo info, TextStyleOptions style)
|
||||
{
|
||||
var transformBuf = LiCommandBuffer.uncleared();
|
||||
|
||||
var fitBox = style.getFitBox();
|
||||
|
||||
var initialScale = style.getSize() / STBTTFont.PIXEL_HEIGHT;
|
||||
var scaleX = initialScale;
|
||||
var scaleY = initialScale;
|
||||
|
||||
var bounds = info.getBoundingBox().scale(scaleX, scaleY, new Rectanglef());
|
||||
|
||||
if (fitBox != null)
|
||||
{
|
||||
// Rescale in sync in both are set to scale
|
||||
if (style.getOverflowX() == TextStyleOptions.OverflowXStrategy.SCALE_TO_FIT &&
|
||||
style.getOverflowY() == TextStyleOptions.OverflowYStrategy.SCALE_TO_FIT)
|
||||
{
|
||||
var smaller = Math.min(fitBox.lengthX() / bounds.lengthX(), fitBox.lengthY() / bounds.lengthY());
|
||||
var rescale = Math.min(1.0f, smaller);
|
||||
scaleX *= rescale;
|
||||
bounds.scale(rescale, rescale);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (style.getOverflowX() == TextStyleOptions.OverflowXStrategy.SCALE_TO_FIT)
|
||||
{
|
||||
var rescale = Math.min(1.0f, fitBox.lengthX() / bounds.lengthX());
|
||||
scaleX *= rescale;
|
||||
bounds.scale(rescale, 1.0f);
|
||||
}
|
||||
|
||||
if (style.getOverflowY() == TextStyleOptions.OverflowYStrategy.SCALE_TO_FIT)
|
||||
{
|
||||
var rescale = Math.min(1.0f, fitBox.lengthY() / bounds.lengthY());
|
||||
scaleY *= rescale;
|
||||
bounds.scale(1.0f, rescale);
|
||||
}
|
||||
}
|
||||
|
||||
x += switch (style.getHorizontalAlign()) {
|
||||
case START -> fitBox.minX - bounds.minX;
|
||||
case CENTER -> (fitBox.maxX + fitBox.minX) / 2.0f - bounds.lengthX() / 2.0f;
|
||||
case END -> fitBox.maxX - bounds.lengthX();
|
||||
};
|
||||
|
||||
y += switch (style.getVerticalAlign()) {
|
||||
case START -> fitBox.minY - bounds.minY;
|
||||
case CENTER -> (fitBox.maxY + fitBox.minY) / 2.0f - bounds.lengthY() / 2.0f;
|
||||
case END -> fitBox.maxY - bounds.lengthY();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
x += switch (style.getHorizontalAlign()) {
|
||||
case START -> -bounds.minX;
|
||||
case CENTER -> -bounds.minX + -bounds.lengthX() / 2.0f;
|
||||
case END -> -bounds.lengthX();
|
||||
};
|
||||
|
||||
y += switch (style.getVerticalAlign()) {
|
||||
case START -> -bounds.lengthY();
|
||||
case CENTER -> -bounds.lengthY() / 2.0f;
|
||||
case END -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
transformBuf.push(new LiCommandSetTransform(new Matrix3f().scale(scaleX, scaleY, 1.0f).m20(x).m21(y)));
|
||||
|
||||
var buf = info.getDrawCommandBuffer();
|
||||
var commandParser = new PlutoGUICommandParser();
|
||||
commandParser.add(LiCommandBuffer.cleared());
|
||||
commandParser.add(transformBuf);
|
||||
commandParser.add(buf);
|
||||
|
||||
try (var drawCalls = commandParser.parse())
|
||||
{
|
||||
drawCalls.render();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package org.plutoengine.graphics;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.graphics.gui.FontShader;
|
||||
import org.plutoengine.graphics.texture.MagFilter;
|
||||
|
@ -34,10 +33,6 @@ public class PlutoGUIMod implements IModEntryPoint
|
|||
instance = mod;
|
||||
|
||||
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
|
||||
fontShader.start();
|
||||
fontShader.recolor.load(1, 1, 1, 1);
|
||||
fontShader.transformationMatrix.load(new Matrix3f());
|
||||
|
||||
|
||||
uiElementsAtlas = new RectangleTexture();
|
||||
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package org.plutoengine.graphics;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.plutoengine.graphics.gui.PlutoGUICommandParser;
|
||||
import org.plutoengine.graphics.gui.STBTTBasicTextShaper;
|
||||
import org.plutoengine.graphics.gui.STBTTFont;
|
||||
import org.plutoengine.libra.command.LiCommandBuffer;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
|
||||
import org.plutoengine.libra.text.shaping.TextShaper;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class TestFontRenderer
|
||||
{
|
||||
public static void drawString(STBTTFont font, String text)
|
||||
{
|
||||
var shaper = new STBTTBasicTextShaper();
|
||||
var info = shaper.shape(EnumSet.of(TextShaper.EnumFeature.KERNING), font, text);
|
||||
|
||||
var transformBuf = new LiCommandBuffer();
|
||||
transformBuf.push(new LiCommandSetTransform(new Matrix3f().scale(.75f).m20(400f).m21(400f)));
|
||||
|
||||
var buf = info.getDrawCommandBuffer();
|
||||
var commandParser = new PlutoGUICommandParser();
|
||||
commandParser.add(transformBuf);
|
||||
commandParser.add(buf);
|
||||
var drawCalls = commandParser.parse();
|
||||
|
||||
drawCalls.render();
|
||||
|
||||
drawCalls.close();
|
||||
}
|
||||
}
|
|
@ -2,14 +2,18 @@ package org.plutoengine.graphics.gui;
|
|||
|
||||
import org.joml.Matrix3fc;
|
||||
import org.plutoengine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import org.plutoengine.libra.paint.LiColorPaint;
|
||||
import org.plutoengine.libra.paint.LiGradientPaint;
|
||||
import org.plutoengine.libra.paint.LiPaint;
|
||||
import org.plutoengine.shader.ShaderBase;
|
||||
import org.plutoengine.shader.ShaderProgram;
|
||||
import org.plutoengine.shader.VertexArrayAttribute;
|
||||
import org.plutoengine.shader.uniform.*;
|
||||
import org.plutoengine.shader.uniform.auto.AutoViewportProjection;
|
||||
import org.plutoengine.util.color.IRGBA;
|
||||
|
||||
@ShaderProgram
|
||||
public final class FontShader extends ShaderBase implements IGUIShader
|
||||
public final class FontShader extends ShaderBase implements ISDFTextShader
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
|
@ -19,10 +23,25 @@ public final class FontShader extends ShaderBase implements IGUIShader
|
|||
public UniformMat3 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformRGBA recolor;
|
||||
public UniformInt paintType;
|
||||
|
||||
@Uniform
|
||||
public UniformBoolean italic;
|
||||
public UniformRGBA paintColor;
|
||||
|
||||
@Uniform
|
||||
public UniformInt paintGradientStopCount;
|
||||
|
||||
@Uniform
|
||||
public UniformArrayRGBA paintGradientColors;
|
||||
|
||||
@Uniform
|
||||
public UniformArrayFloat paintGradientPositions;
|
||||
|
||||
@Uniform
|
||||
public UniformArrayVec2 paintGradientEnds;
|
||||
|
||||
@Uniform
|
||||
public UniformFloat pxScale;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
@ -33,9 +52,54 @@ public final class FontShader extends ShaderBase implements IGUIShader
|
|||
@VertexArrayAttribute(2)
|
||||
public int page;
|
||||
|
||||
@VertexArrayAttribute(3)
|
||||
public int paintUVCoords;
|
||||
|
||||
@Override
|
||||
public void setTransform(Matrix3fc transform)
|
||||
{
|
||||
this.transformationMatrix.load(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaint(LiPaint paint)
|
||||
{
|
||||
switch (paint.getType())
|
||||
{
|
||||
case SOLID_COLOR -> {
|
||||
var col = ((LiColorPaint) paint).getColor();
|
||||
this.paintType.load(0);
|
||||
this.paintColor.load(col.getFloatComponentsRGBA());
|
||||
}
|
||||
|
||||
case GRADIENT -> {
|
||||
var gradPaint = (LiGradientPaint) paint;
|
||||
this.paintType.load(1);
|
||||
this.paintGradientEnds.load(gradPaint.getStart(), gradPaint.getEnd());
|
||||
var stops = gradPaint.getStops();
|
||||
this.paintGradientStopCount.load(stops.length);
|
||||
var colors = new IRGBA[stops.length];
|
||||
var positions = new float[stops.length];
|
||||
|
||||
int i = 0;
|
||||
for (var stop : stops)
|
||||
{
|
||||
var col = stop.color();
|
||||
|
||||
colors[i] = col.getFloatComponentsRGBA();
|
||||
positions[i] = stop.position();
|
||||
i++;
|
||||
}
|
||||
|
||||
this.paintGradientColors.load(colors);
|
||||
this.paintGradientPositions.load(positions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPixelScale(float scale)
|
||||
{
|
||||
this.pxScale.load(scale);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.plutoengine.graphics.gui;
|
||||
|
||||
import org.joml.Matrix3fc;
|
||||
import org.plutoengine.libra.paint.LiPaint;
|
||||
|
||||
public interface IGUIShader
|
||||
{
|
||||
void setTransform(Matrix3fc transform);
|
||||
|
||||
void setPaint(LiPaint paint);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.plutoengine.graphics.gui;
|
||||
|
||||
public interface ISDFTextShader extends IGUIShader
|
||||
{
|
||||
void setPixelScale(float scale);
|
||||
}
|
|
@ -4,12 +4,15 @@ import org.plutoengine.graphics.gl.DrawMode;
|
|||
import org.plutoengine.graphics.gl.vao.VertexArray;
|
||||
import org.plutoengine.graphics.gl.vao.VertexArrayBuilder;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMesh;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
|
||||
import org.plutoengine.libra.command.AbstractGUICommandParser;
|
||||
import org.plutoengine.libra.command.IGUIRenderer;
|
||||
import org.plutoengine.libra.command.LiCommandBuffer;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSpecial;
|
||||
import org.plutoengine.shader.ShaderBase;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
@ -52,6 +55,9 @@ public class PlutoGUICommandParser extends AbstractGUICommandParser
|
|||
attribsToEnable.removeAll(alreadyEnabledAttribs);
|
||||
alreadyEnabledAttribs.addAll(attribsToEnable);
|
||||
|
||||
if (drawCmd instanceof PlutoCommandDrawMeshDirectBuffer dBuf)
|
||||
dBuf.close();
|
||||
|
||||
drawCalls.add(() -> {
|
||||
vao.bind();
|
||||
attribsToEnable.forEach(VertexArray::enableAttribute);
|
||||
|
@ -72,6 +78,17 @@ public class PlutoGUICommandParser extends AbstractGUICommandParser
|
|||
drawCalls.add(() -> shaderCapture.setTransform(transformCmd.getTransform()));
|
||||
}
|
||||
|
||||
case SET_PAINT -> {
|
||||
if (!(cmd instanceof LiCommandSetPaint paintCmd))
|
||||
throw new IllegalStateException();
|
||||
|
||||
var shaderCapture = currentShader;
|
||||
|
||||
assert shaderCapture != null;
|
||||
|
||||
drawCalls.add(() -> shaderCapture.setPaint(paintCmd.getPaint()));
|
||||
}
|
||||
|
||||
case SWITCH_SHADER -> {
|
||||
if (!(cmd instanceof PlutoCommandSwitchShader swSh))
|
||||
throw new IllegalStateException();
|
||||
|
@ -93,6 +110,14 @@ public class PlutoGUICommandParser extends AbstractGUICommandParser
|
|||
|
||||
drawCalls.add(textureCapture::bind);
|
||||
}
|
||||
|
||||
case SPECIAL -> {
|
||||
if (!(cmd instanceof LiCommandSpecial cSp))
|
||||
throw new IllegalStateException();
|
||||
|
||||
var af = cSp.getAction();
|
||||
drawCalls.add(() -> af.accept(mergedBuffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
package org.plutoengine.graphics.gui;
|
||||
|
||||
import org.plutoengine.libra.command.LiCommandBuffer;
|
||||
import org.plutoengine.libra.text.LiTextInfo;
|
||||
|
||||
public class PlutoTextInfo extends LiTextInfo
|
||||
{
|
||||
protected PlutoTextInfo(LiCommandBuffer commandBuffer)
|
||||
{
|
||||
super(commandBuffer);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,21 @@
|
|||
package org.plutoengine.graphics.gui.command;
|
||||
|
||||
import org.plutoengine.graphics.gl.vao.attrib.AttributeInfo;
|
||||
import org.plutoengine.graphics.gl.vbo.EnumArrayBufferType;
|
||||
import org.plutoengine.libra.command.impl.LiCommand;
|
||||
import org.plutoengine.libra.command.impl.LiCommandDrawMesh;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class PlutoCommandDrawMesh extends LiCommandDrawMesh
|
||||
public abstract sealed class PlutoCommandDrawMesh extends LiCommandDrawMesh permits PlutoCommandDrawMeshDirectBuffer, PlutoCommandDrawMeshHeap
|
||||
{
|
||||
private final Map<Integer, AttributeInfo> attributeInfo;
|
||||
private final Map<Integer, Buffer> data;
|
||||
private IntBuffer indices;
|
||||
protected final Map<Integer, AttributeInfo> attributeInfo;
|
||||
protected final Map<Integer, Buffer> data;
|
||||
protected IntBuffer indices;
|
||||
|
||||
public PlutoCommandDrawMesh()
|
||||
{
|
||||
|
@ -48,19 +49,7 @@ public class PlutoCommandDrawMesh extends LiCommandDrawMesh
|
|||
this.addIndices(IntBuffer.wrap(data));
|
||||
}
|
||||
|
||||
public void addIndices(IntBuffer data)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (this.indices == null)
|
||||
this.indices = IntBuffer.allocate(data.remaining());
|
||||
|
||||
if (this.indices.remaining() < data.remaining())
|
||||
this.indices = IntBuffer.allocate(Math.max(this.indices.capacity() << 1, this.indices.capacity() + data.remaining())).put(this.indices.flip());
|
||||
|
||||
this.indices.put(data);
|
||||
}
|
||||
public abstract void addIndices(IntBuffer data);
|
||||
|
||||
public void addAttribute(int attrib, float[] data, int dimensions)
|
||||
{
|
||||
|
@ -70,30 +59,7 @@ public class PlutoCommandDrawMesh extends LiCommandDrawMesh
|
|||
this.addAttribute(attrib, FloatBuffer.wrap(data), dimensions);
|
||||
}
|
||||
|
||||
public void addAttribute(int attrib, FloatBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.FLOAT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
return FloatBuffer.allocate(data.remaining()).put(data);
|
||||
|
||||
if (!(v instanceof FloatBuffer fab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() <= fab.remaining())
|
||||
return fab.put(data);
|
||||
|
||||
var newBuf = FloatBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
|
||||
return newBuf.put(fab.flip()).put(data);
|
||||
});
|
||||
}
|
||||
public abstract void addAttribute(int attrib, FloatBuffer data, int dimensions);
|
||||
|
||||
public void addAttribute(int attrib, int[] data, int dimensions)
|
||||
{
|
||||
|
@ -103,30 +69,7 @@ public class PlutoCommandDrawMesh extends LiCommandDrawMesh
|
|||
this.addAttribute(attrib, IntBuffer.wrap(data), dimensions);
|
||||
}
|
||||
|
||||
public void addAttribute(int attrib, IntBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.INT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
return IntBuffer.allocate(data.remaining()).put(data);
|
||||
|
||||
if (!(v instanceof IntBuffer fab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() <= fab.remaining())
|
||||
return fab.put(data);
|
||||
|
||||
var newBuf = IntBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
|
||||
return newBuf.put(fab.flip()).put(data);
|
||||
});
|
||||
}
|
||||
public abstract void addAttribute(int attrib, IntBuffer data, int dimensions);
|
||||
|
||||
@Override
|
||||
public boolean supportsMerge(LiCommand other)
|
||||
|
@ -154,7 +97,7 @@ public class PlutoCommandDrawMesh extends LiCommandDrawMesh
|
|||
}
|
||||
});
|
||||
|
||||
this.addIndices(pcdm.indices);
|
||||
this.addIndices(pcdm.getIndices());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package org.plutoengine.graphics.gui.command;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.graphics.gl.vao.attrib.AttributeInfo;
|
||||
import org.plutoengine.graphics.gl.vbo.EnumArrayBufferType;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public final class PlutoCommandDrawMeshDirectBuffer extends PlutoCommandDrawMesh implements AutoCloseable
|
||||
{
|
||||
public void addIndices(IntBuffer data)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (this.indices == null)
|
||||
{
|
||||
if (data.isDirect())
|
||||
{
|
||||
this.indices = data;
|
||||
var size = this.indices.remaining();
|
||||
this.indices.limit(size);
|
||||
this.indices.position(size);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.indices = MemoryUtil.memAllocInt(data.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.indices.remaining() < data.remaining())
|
||||
{
|
||||
var newSize = Math.max(this.indices.capacity() << 1, this.indices.capacity() + data.remaining());
|
||||
this.indices = MemoryUtil.memRealloc(this.indices, newSize);
|
||||
}
|
||||
|
||||
this.indices.put(data);
|
||||
|
||||
if (data.isDirect())
|
||||
MemoryUtil.memFree(data);
|
||||
}
|
||||
|
||||
public void addAttribute(int attrib, FloatBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.FLOAT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
{
|
||||
if (data.isDirect())
|
||||
{
|
||||
var size = data.remaining();
|
||||
data.limit(size);
|
||||
data.position(size);
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MemoryUtil.memAllocFloat(data.remaining()).put(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(v instanceof FloatBuffer fab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() > fab.remaining())
|
||||
{
|
||||
var newSize = Math.max(fab.capacity() << 1, fab.capacity() + data.remaining());
|
||||
fab = MemoryUtil.memRealloc(fab, newSize);
|
||||
}
|
||||
|
||||
fab.put(data);
|
||||
|
||||
if (data.isDirect())
|
||||
MemoryUtil.memFree(data);
|
||||
|
||||
return fab;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void addAttribute(int attrib, IntBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.INT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
{
|
||||
if (data.isDirect())
|
||||
{
|
||||
var size = data.remaining();
|
||||
data.limit(size);
|
||||
data.position(size);
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MemoryUtil.memAllocInt(data.remaining()).put(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(v instanceof IntBuffer iab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() > iab.remaining())
|
||||
{
|
||||
var newSize = Math.max(iab.capacity() << 1, iab.capacity() + data.remaining());
|
||||
iab = MemoryUtil.memRealloc(iab, newSize);
|
||||
}
|
||||
|
||||
iab.put(data);
|
||||
|
||||
if (data.isDirect())
|
||||
MemoryUtil.memFree(data);
|
||||
|
||||
return iab;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.indices);
|
||||
|
||||
this.data.values()
|
||||
.forEach(MemoryUtil::memFree);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.plutoengine.graphics.gui.command;
|
||||
|
||||
import org.plutoengine.graphics.gl.vao.attrib.AttributeInfo;
|
||||
import org.plutoengine.graphics.gl.vbo.EnumArrayBufferType;
|
||||
import org.plutoengine.libra.command.impl.LiCommand;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public final class PlutoCommandDrawMeshHeap extends PlutoCommandDrawMesh
|
||||
{
|
||||
public void addIndices(IntBuffer data)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (this.indices == null)
|
||||
this.indices = IntBuffer.allocate(data.remaining());
|
||||
|
||||
if (this.indices.remaining() < data.remaining())
|
||||
this.indices = IntBuffer.allocate(Math.max(this.indices.capacity() << 1, this.indices.capacity() + data.remaining())).put(this.indices.flip());
|
||||
|
||||
this.indices.put(data);
|
||||
}
|
||||
|
||||
public void addAttribute(int attrib, FloatBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.FLOAT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
return FloatBuffer.allocate(data.remaining()).put(data);
|
||||
|
||||
if (!(v instanceof FloatBuffer fab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() <= fab.remaining())
|
||||
return fab.put(data);
|
||||
|
||||
var newBuf = FloatBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
|
||||
return newBuf.put(fab.flip()).put(data);
|
||||
});
|
||||
}
|
||||
|
||||
public void addAttribute(int attrib, IntBuffer data, int dimensions)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.INT, attrib, dimensions));
|
||||
|
||||
if (meta.dimensions() != dimensions)
|
||||
throw new IllegalArgumentException("Attribute dimensions mismatch!");
|
||||
|
||||
this.data.compute(attrib, (k, v) -> {
|
||||
if (v == null)
|
||||
return IntBuffer.allocate(data.remaining()).put(data);
|
||||
|
||||
if (!(v instanceof IntBuffer iab))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (data.remaining() <= iab.remaining())
|
||||
return iab.put(data);
|
||||
|
||||
var newBuf = IntBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
|
||||
return newBuf.put(iab.flip()).put(data);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlutoCommandDrawMeshHeap merge(LiCommand other)
|
||||
{
|
||||
if (!(other instanceof PlutoCommandDrawMesh pcdm))
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
pcdm.data.forEach((k, v) -> {
|
||||
var attrInfo = this.attributeInfo.get(k);
|
||||
|
||||
switch (attrInfo.type())
|
||||
{
|
||||
case FLOAT -> this.addAttribute(k, (FloatBuffer) v, attrInfo.dimensions());
|
||||
case INT -> this.addAttribute(k, (IntBuffer) v, attrInfo.dimensions());
|
||||
case UNSIGNED_INT -> throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
this.addIndices(pcdm.indices);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,26 +1,42 @@
|
|||
package org.plutoengine.graphics.gui;
|
||||
package org.plutoengine.graphics.gui.stbttf;
|
||||
|
||||
import org.joml.primitives.Rectanglef;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.graphics.PlutoGUIMod;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMesh;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
|
||||
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
|
||||
import org.plutoengine.libra.command.LiCommandBuffer;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
|
||||
import org.plutoengine.libra.command.impl.LiCommandSpecial;
|
||||
import org.plutoengine.libra.text.LiTextInfo;
|
||||
import org.plutoengine.libra.text.font.GlyphInfo;
|
||||
import org.plutoengine.libra.text.font.LiFontFamily;
|
||||
import org.plutoengine.libra.text.shaping.IShapingStrategy;
|
||||
import org.plutoengine.libra.text.shaping.TextShaper;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
|
||||
public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGlyphMetrics, STBTTFont.STBTTGlyphAtlas, STBTTFont>
|
||||
{
|
||||
private LiCommandBuffer commandBuffer;
|
||||
|
||||
public STBTTBasicTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
|
||||
{
|
||||
this.commandBuffer = commandBuffer;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, STBTTFont font, String text)
|
||||
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, LiFontFamily<STBTTFont> fontFamily, String text, TextStyleOptions style)
|
||||
{
|
||||
var commandBuf = new LiCommandBuffer();
|
||||
var commandBuf = Objects.requireNonNullElseGet(this.commandBuffer, LiCommandBuffer::uncleared);
|
||||
|
||||
var font = style.pickFont(fontFamily);
|
||||
|
||||
var atlas = font.getGlyphAtlas();
|
||||
var atlasTexture = atlas.getGlyphAtlasTexture();
|
||||
|
@ -31,9 +47,11 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
var shaderSwitch = new PlutoCommandSwitchShader(shader);
|
||||
commandBuf.push(shaderSwitch);
|
||||
|
||||
commandBuf.push(new LiCommandSetPaint(style.getPaint()));
|
||||
|
||||
var cpCount = (int) text.codePoints().count();
|
||||
|
||||
var mesh = new PlutoCommandDrawMesh();
|
||||
var mesh = new PlutoCommandDrawMeshDirectBuffer();
|
||||
|
||||
final var quadVerts = 4;
|
||||
final var twoTriVerts = 6;
|
||||
|
@ -44,6 +62,8 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
var uvDim = 2;
|
||||
var uvBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
|
||||
|
||||
var paintUVBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
|
||||
|
||||
var pageDim = 1;
|
||||
var pageBuf = MemoryUtil.memAllocInt(pageDim * quadVerts * cpCount);
|
||||
|
||||
|
@ -60,14 +80,17 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
float[] uvs = new float[uvDim * quadVerts];
|
||||
int[] pages = new int[pageDim * quadVerts];
|
||||
|
||||
float scale = 1 / (float) STBTTFont.PIXEL_HEIGHT;
|
||||
float scale = font.getScale();
|
||||
|
||||
commandBuf.push(new LiCommandSpecial(cb -> PlutoGUIMod.fontShader.setPixelScale(style.getSize())));
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
|
||||
GlyphInfo<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTTGlyphMetrics> info;
|
||||
GlyphInfo<?, ?> info;
|
||||
STBTTFont.STBTTGlyphMetrics metrics = null;
|
||||
int cp;
|
||||
float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY, minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;
|
||||
|
||||
while (cpIt.hasNext())
|
||||
{
|
||||
|
@ -82,8 +105,8 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
}
|
||||
}
|
||||
|
||||
if (metrics != null)
|
||||
x += metrics.getKerning(cp);
|
||||
if (metrics != null && features.contains(TextShaper.EnumFeature.KERNING))
|
||||
x += metrics.getKerning(cp) * scale;
|
||||
|
||||
metrics = font.getGlyphMetrics(cp);
|
||||
info = atlas.getGlyph(cp);
|
||||
|
@ -91,17 +114,25 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
if (metrics == null)
|
||||
continue;
|
||||
|
||||
x += metrics.getLeftSideBearing() * scale;
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
float gx = x;
|
||||
float gy = y + font.getAscent() * scale - metrics.getCY0() * scale;
|
||||
float gx = x + metrics.getXOrigin();
|
||||
float gy = y + metrics.getYOrigin() + font.getAscent() * scale;
|
||||
|
||||
vertices[6] = vertices[0] = gx - STBTTFont.SDF_PADDING;
|
||||
vertices[3] = vertices[1] = gy + STBTTFont.SDF_PADDING;
|
||||
vertices[4] = vertices[2] = gx + metrics.getCX1() * scale - metrics.getCX0() * scale + STBTTFont.SDF_PADDING;
|
||||
vertices[7] = vertices[5] = gy - metrics.getCY1() * scale + metrics.getCY0() * scale - STBTTFont.SDF_PADDING;
|
||||
float xLow = gx - STBTTFont.SDF_PADDING;
|
||||
float xHigh = gx + metrics.getCX1() * scale - metrics.getCX0() * scale + STBTTFont.SDF_PADDING;
|
||||
float yLow = gy - STBTTFont.SDF_PADDING;
|
||||
float yHigh = gy + metrics.getCY1() * scale - metrics.getCY0() * scale + STBTTFont.SDF_PADDING;
|
||||
|
||||
minX = Math.min(minX, xLow);
|
||||
minY = Math.min(minY, yLow);
|
||||
maxX = Math.max(maxX, xHigh);
|
||||
maxY = Math.max(maxY, yHigh);
|
||||
|
||||
vertices[6] = vertices[0] = xLow;
|
||||
vertices[3] = vertices[1] = yHigh;
|
||||
vertices[4] = vertices[2] = xHigh;
|
||||
vertices[7] = vertices[5] = yLow;
|
||||
|
||||
var uvRect = info.getRect();
|
||||
uvs[6] = uvs[0] = uvRect.minX;
|
||||
|
@ -128,14 +159,23 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
|
|||
x += metrics.getAdvanceX() * scale;
|
||||
}
|
||||
|
||||
mesh.addAttribute(shader.position, vertexBuf.flip(), vertDim);
|
||||
vertexBuf.flip();
|
||||
|
||||
while (vertexBuf.hasRemaining())
|
||||
{
|
||||
paintUVBuf.put((vertexBuf.get() - minX) / (maxX - minX));
|
||||
paintUVBuf.put((vertexBuf.get() - minY) / (maxY - minY));
|
||||
}
|
||||
|
||||
mesh.addAttribute(shader.position, vertexBuf.rewind(), vertDim);
|
||||
mesh.addAttribute(shader.uvCoords, uvBuf.flip(), uvDim);
|
||||
mesh.addAttribute(shader.page, pageBuf.flip(), pageDim);
|
||||
mesh.addAttribute(shader.paintUVCoords, paintUVBuf.flip(), uvDim);
|
||||
|
||||
mesh.addIndices(indexBuf.flip());
|
||||
|
||||
commandBuf.push(mesh);
|
||||
|
||||
return new PlutoTextInfo(commandBuf);
|
||||
return new LiTextInfo(commandBuf, new Rectanglef(minX, minY, maxX, maxY));
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package org.plutoengine.graphics.gui;
|
||||
package org.plutoengine.graphics.gui.stbttf;
|
||||
|
||||
import org.joml.primitives.Rectanglef;
|
||||
import org.lwjgl.stb.*;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.plutoengine.buffer.BufferHelper;
|
||||
import org.plutoengine.graphics.gui.SDFTextureArray;
|
||||
import org.plutoengine.graphics.texture.MagFilter;
|
||||
import org.plutoengine.graphics.texture.MinFilter;
|
||||
import org.plutoengine.graphics.texture.WrapMode;
|
||||
|
@ -29,7 +30,7 @@ import java.util.stream.IntStream;
|
|||
public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTTGlyphMetrics> implements AutoCloseable
|
||||
{
|
||||
public static final int PIXEL_HEIGHT = 64;
|
||||
public static final int SDF_PADDING = 4;
|
||||
public static final int SDF_PADDING = 8;
|
||||
public static final int SHEET_SIZE = 1024;
|
||||
|
||||
private int descent;
|
||||
|
@ -37,6 +38,10 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
private int lineGap;
|
||||
private int lineAdvance;
|
||||
|
||||
private float scale;
|
||||
|
||||
private Map<Integer, Integer> glyphIndexLookup;
|
||||
|
||||
private Map<KerningPair, Integer> kerningTable;
|
||||
|
||||
private record KerningPair(int left, int right)
|
||||
|
@ -49,6 +54,11 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
super(name);
|
||||
}
|
||||
|
||||
public float getScale()
|
||||
{
|
||||
return this.scale;
|
||||
}
|
||||
|
||||
public int getAscent()
|
||||
{
|
||||
return this.ascent;
|
||||
|
@ -71,7 +81,8 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
|
||||
public int getKerningOffset(int left, int right)
|
||||
{
|
||||
return this.kerningTable.getOrDefault(new KerningPair(left, right), 0);
|
||||
var sk = new KerningPair(glyphIndexLookup.getOrDefault(left, -1), glyphIndexLookup.getOrDefault(right, -1));
|
||||
return this.kerningTable.getOrDefault(sk, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -181,8 +192,6 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
return null;
|
||||
}
|
||||
|
||||
float scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, PIXEL_HEIGHT);
|
||||
|
||||
var nameBuf = STBTruetype.stbtt_GetFontNameString(fontInfo,
|
||||
STBTruetype.STBTT_PLATFORM_ID_MICROSOFT,
|
||||
STBTruetype.STBTT_MS_EID_UNICODE_BMP,
|
||||
|
@ -201,6 +210,7 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
Logger.logf(SmartSeverity.ADDED, "Loading font: %s%n", name);
|
||||
|
||||
var font = new STBTTFont(name);
|
||||
font.scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, PIXEL_HEIGHT);
|
||||
|
||||
var ascentBuf = stack.callocInt(1);
|
||||
var descentBuf = stack.callocInt(1);
|
||||
|
@ -209,9 +219,6 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
|
||||
var codepoints = IntStream.rangeClosed(0x0000, 0x04ff);
|
||||
|
||||
var onedgeValue = 220;
|
||||
var pixelDistScale = onedgeValue / (float) SDF_PADDING;
|
||||
|
||||
var sdfWidthBuf = stack.mallocInt(1);
|
||||
var sdfHeightBuf = stack.mallocInt(1);
|
||||
var xOffsBuf = stack.mallocInt(1);
|
||||
|
@ -232,6 +239,7 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
var colorModel = new ComponentColorModel(colorSpace, new int[] { 8 }, false,false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
||||
|
||||
font.atlas = font.new STBTTGlyphAtlas();
|
||||
font.glyphIndexLookup = new HashMap<>();
|
||||
|
||||
var advanceWidth = stack.mallocInt(1);
|
||||
var leftSideBearing = stack.mallocInt(1);
|
||||
|
@ -241,10 +249,13 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
var cx1 = stack.mallocInt(1);
|
||||
var cy1 = stack.mallocInt(1);
|
||||
|
||||
var onedgeValue = 128;
|
||||
var pixelDistScale = onedgeValue / (float) SDF_PADDING;
|
||||
|
||||
for (var cp : codepoints.toArray())
|
||||
{
|
||||
var buf = STBTruetype.stbtt_GetCodepointSDF(fontInfo,
|
||||
scale,
|
||||
font.scale,
|
||||
cp,
|
||||
SDF_PADDING,
|
||||
(byte) onedgeValue,
|
||||
|
@ -324,6 +335,8 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
glyphMetrics.cy1 = cy1.get(0);
|
||||
|
||||
font.addGlyph(cp, glyphMetrics, glyphInfo);
|
||||
|
||||
font.glyphIndexLookup.put(cp, STBTruetype.stbtt_FindGlyphIndex(fontInfo, cp));
|
||||
}
|
||||
|
||||
if (!sheets.contains(atlas))
|
||||
|
@ -332,10 +345,16 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
|
|||
nodes.free();
|
||||
|
||||
var kerningTableLength = STBTruetype.stbtt_GetKerningTableLength(fontInfo);
|
||||
var kerningTable = STBTTKerningentry.malloc(kerningTableLength, stack);
|
||||
STBTruetype.stbtt_GetKerningTable(fontInfo, kerningTable);
|
||||
font.kerningTable = new HashMap<>();
|
||||
kerningTable.forEach(e -> font.kerningTable.put(new KerningPair(e.glyph1(), e.glyph2()), e.advance()));
|
||||
try (var kerningTable = STBTTKerningentry.malloc(kerningTableLength))
|
||||
{
|
||||
STBTruetype.stbtt_GetKerningTable(fontInfo, kerningTable);
|
||||
font.kerningTable = new HashMap<>();
|
||||
kerningTable.forEach(e -> {
|
||||
// System.out.printf("%s -> %s = %d%n", Character.toString(e.glyph1()), Character.toString(e.glyph2()), e.advance());
|
||||
|
||||
font.kerningTable.put(new KerningPair(e.glyph1(), e.glyph2()), e.advance());
|
||||
});
|
||||
}
|
||||
|
||||
font.ascent = ascentBuf.get();
|
||||
font.descent = descentBuf.get();
|
|
@ -9,9 +9,6 @@ import org.plutoengine.graphics.gl.vbo.IndexArrayBuffer;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import org.plutoengine.logger.Logger;
|
||||
import org.plutoengine.logger.SmartSeverity;
|
||||
|
||||
public class VertexArray implements AutoCloseable
|
||||
{
|
||||
protected final List<Integer> usedAttributes;
|
||||
|
@ -136,8 +133,6 @@ public class VertexArray implements AutoCloseable
|
|||
this.indices = null;
|
||||
}
|
||||
|
||||
Logger.logf(SmartSeverity.REMOVED, "Vertex array ID %d deleted...\n", this.glID);
|
||||
|
||||
GL33.glDeleteVertexArrays(this.glID);
|
||||
|
||||
this.glID = 0;
|
||||
|
|
|
@ -31,7 +31,7 @@ public enum SmartSeverity implements ISeverity
|
|||
MODULE_MINUS("[-] [M] ", false),
|
||||
MODULE_WARNING("[!] [M] ", true),
|
||||
MODULE_ERROR("[X] [M] ", true),
|
||||
MODULE_CHECK("[✓] [M] ", true),
|
||||
MODULE_CHECK("[✓] [M] ", false),
|
||||
|
||||
EVENT("[i] [E] ", false),
|
||||
EVENT_PLUS("[+] [E] ", false),
|
||||
|
|
Binary file not shown.
|
@ -10,6 +10,10 @@
|
|||
"fonts": {
|
||||
"path": "fonts.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"plutofonts": {
|
||||
"path": "plutofonts",
|
||||
"type": "open"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -2,25 +2,67 @@
|
|||
|
||||
in vec2 uvCoordinates;
|
||||
flat in int atlasPage;
|
||||
in vec2 paintUVCoordinates;
|
||||
|
||||
uniform sampler2DArray textureSampler;
|
||||
uniform vec4 recolor;
|
||||
|
||||
uniform int paintType;
|
||||
uniform vec4 paintColor;
|
||||
|
||||
uniform int paintGradientStopCount;
|
||||
uniform vec4[16] paintGradientColors;
|
||||
uniform float[16] paintGradientPositions;
|
||||
uniform vec2[2] paintGradientEnds;
|
||||
|
||||
uniform float pxScale;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
vec4 solidColor(void)
|
||||
{
|
||||
return paintColor;
|
||||
}
|
||||
|
||||
vec4 gradientColor(void)
|
||||
{
|
||||
float par = smoothstep(paintGradientEnds[0].x, paintGradientEnds[1].x, paintUVCoordinates.x) * (paintGradientStopCount - 1);
|
||||
|
||||
float nPar = clamp(par, 0, float(paintGradientStopCount - 1)) + paintGradientPositions[0] * 0.000001;
|
||||
vec4 lCol = paintGradientColors[int(floor(nPar))];
|
||||
vec4 rCol = paintGradientColors[int(ceil(nPar))];
|
||||
|
||||
float gamma = 0.45;
|
||||
|
||||
vec4 ilCol = vec4(pow(lCol.r, 1 / gamma), pow(lCol.g, 1 / gamma), pow(lCol.b, 1 / gamma), lCol.a);
|
||||
vec4 irCol = vec4(pow(rCol.r, 1 / gamma), pow(rCol.g, 1 / gamma), pow(rCol.b, 1 / gamma), rCol.a);
|
||||
|
||||
vec4 fCol = mix(ilCol, irCol, fract(nPar));
|
||||
|
||||
return vec4(pow(fCol.r, gamma), pow(fCol.g, gamma), pow(fCol.b, gamma), fCol.a);
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 textCoords = vec3(uvCoordinates, atlasPage);
|
||||
vec3 texCoords = vec3(uvCoordinates, atlasPage);
|
||||
|
||||
float threshold = 1 - 240.0 / 255.0;
|
||||
float threshold = 180.0 / 255.0 - 5.0 / pow(pxScale, 1.6); // Also help small text be readable
|
||||
|
||||
float signedDist = 1 - texture(textureSampler, textCoords).r;
|
||||
float signedDist = texture(textureSampler, texCoords).r - threshold;
|
||||
|
||||
float fw = fwidth(signedDist);
|
||||
vec4 col;
|
||||
|
||||
vec4 col = recolor;
|
||||
switch (paintType)
|
||||
{
|
||||
case 0:
|
||||
col = solidColor();
|
||||
break;
|
||||
|
||||
col.a *= smoothstep(threshold + 0.2, threshold, signedDist);
|
||||
case 1:
|
||||
col = gradientColor();
|
||||
break;
|
||||
}
|
||||
|
||||
col.a *= smoothstep(0, 2.4 / pxScale, signedDist);
|
||||
|
||||
out_Color = col;
|
||||
}
|
|
@ -3,20 +3,20 @@
|
|||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
in int page;
|
||||
in vec2 paintUVCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
out vec2 paintUVCoordinates;
|
||||
flat out int atlasPage;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3 transformation;
|
||||
|
||||
uniform int italic;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec2 pos = vec2(position.x - position.y * italic / 4.0, position.y);
|
||||
atlasPage = page;
|
||||
uvCoordinates = uvCoords;
|
||||
vec3 transformed = vec3((transformation * vec3(pos, 1.0)).xy, 0.0);
|
||||
paintUVCoordinates = paintUVCoords;
|
||||
vec3 transformed = vec3((transformation * vec3(position, 1.0)).xy, 0.0);
|
||||
gl_Position = projection * vec4(transformed, 1.0);
|
||||
}
|
|
@ -1,7 +1,13 @@
|
|||
package org.plutoengine.demo;
|
||||
|
||||
import org.plutoengine.graphics.PlutoGUIMod;
|
||||
import org.plutoengine.graphics.gui.STBTTFont;
|
||||
import org.plutoengine.graphics.gui.stbttf.STBTTFont;
|
||||
import org.plutoengine.graphics.texture.MagFilter;
|
||||
import org.plutoengine.graphics.texture.MinFilter;
|
||||
import org.plutoengine.graphics.texture.WrapMode;
|
||||
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
|
||||
import org.plutoengine.libra.text.font.LiFontFamily;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
import org.plutoengine.mod.IModEntryPoint;
|
||||
import org.plutoengine.mod.Mod;
|
||||
import org.plutoengine.mod.ModEntry;
|
||||
|
@ -13,17 +19,25 @@ import org.plutoengine.mod.ModEntry;
|
|||
)
|
||||
public class BasicApplicationDemoMod implements IModEntryPoint
|
||||
{
|
||||
public static STBTTFont font;
|
||||
public static LiFontFamily<STBTTFont> font;
|
||||
|
||||
public static RectangleTexture plutoLogo;
|
||||
|
||||
@Override
|
||||
public void onLoad(Mod mod)
|
||||
{
|
||||
font = STBTTFont.load(mod.getResource("fonts$robotoregular#ttf"));
|
||||
font = new LiFontFamily<>();
|
||||
font.add(TextStyleOptions.STYLE_REGULAR, STBTTFont.load(mod.getResource("plutofonts$plutostardust#ttf")));
|
||||
|
||||
plutoLogo = new RectangleTexture();
|
||||
plutoLogo.load(mod.getResource("icons$icon128#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload()
|
||||
{
|
||||
plutoLogo.close();
|
||||
|
||||
font.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,40 @@
|
|||
package org.plutoengine.demo;
|
||||
|
||||
import org.joml.primitives.Rectanglef;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.PlutoApplication;
|
||||
import org.plutoengine.PlutoLocal;
|
||||
import org.plutoengine.display.Display;
|
||||
import org.plutoengine.display.Framerate;
|
||||
import org.plutoengine.graphics.ImmediateFontRenderer;
|
||||
import org.plutoengine.graphics.PlutoGUIMod;
|
||||
import org.plutoengine.graphics.Renderer2D;
|
||||
import org.plutoengine.graphics.TestFontRenderer;
|
||||
import org.plutoengine.graphics.gl.vao.QuadPresets;
|
||||
import org.plutoengine.graphics.RectangleRenderer2D;
|
||||
import org.plutoengine.graphics.gui.FontShader;
|
||||
import org.plutoengine.graphics.texture.MagFilter;
|
||||
import org.plutoengine.graphics.texture.MinFilter;
|
||||
import org.plutoengine.graphics.texture.WrapMode;
|
||||
import org.plutoengine.graphics.texture.texture2d.Texture2D;
|
||||
import org.plutoengine.input.InputBus;
|
||||
import org.plutoengine.libra.paint.LiGradientPaint;
|
||||
import org.plutoengine.libra.paint.LiPaint;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
import org.plutoengine.math.ProjectionMatrix;
|
||||
import org.plutoengine.mod.ModLoader;
|
||||
import org.plutoengine.shader.RenderShaderBuilder;
|
||||
import org.plutoengine.shader.uniform.auto.AutomaticUniforms;
|
||||
import org.plutoengine.tpl.ImageLoader;
|
||||
import org.plutoengine.util.color.Color;
|
||||
import org.plutoengine.util.color.EnumColorFormat;
|
||||
import org.plutoengine.util.color.HSB;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main extends PlutoApplication
|
||||
{
|
||||
public static Main INSTANCE;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
public static void main(String[] args)
|
||||
{
|
||||
var config = new PlutoApplication.StartupConfig();
|
||||
config.vsync(1);
|
||||
config.windowName("JSRClone " + VersionInfo.GAME_VERSION);
|
||||
config.windowName("PlutoEngine Demo " + VersionInfo.GAME_VERSION);
|
||||
|
||||
INSTANCE = new Main();
|
||||
INSTANCE.run(args, config);
|
||||
|
@ -42,13 +43,55 @@ public class Main extends PlutoApplication
|
|||
@Override
|
||||
public void loop()
|
||||
{
|
||||
GL33.glEnable(GL11.GL_BLEND);
|
||||
GL33.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL33.glEnable(GL33.GL_BLEND);
|
||||
GL33.glBlendFunc(GL33.GL_SRC_ALPHA, GL33.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
var projection = ProjectionMatrix.createOrtho2D(this.display.getWidth(), this.display.getHeight());
|
||||
AutomaticUniforms.VIEWPORT_PROJECTION.fire(projection);
|
||||
|
||||
TestFontRenderer.drawString(BasicApplicationDemoMod.font, "Testing ");
|
||||
var style = new TextStyleOptions(20.0f);
|
||||
style.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
|
||||
var mods = PlutoLocal.components().getComponent(ModLoader.class).getAllMods();
|
||||
|
||||
var modStr = mods.stream().map(mod -> {
|
||||
var modManifest = mod.getManifest();
|
||||
return String.format("%s %s", modManifest.displayName(), mod.getVersion());
|
||||
}).collect(Collectors.joining("\n"));
|
||||
|
||||
ImmediateFontRenderer.drawString(5, 5, Framerate.getInterpolatedFPS() + " FPS", BasicApplicationDemoMod.font, style);
|
||||
ImmediateFontRenderer.drawString(5, 30, modStr, BasicApplicationDemoMod.font, style);
|
||||
|
||||
var watermarkStyle = new TextStyleOptions(20.0f);
|
||||
watermarkStyle.setHorizontalAlign(TextStyleOptions.TextAlign.CENTER);
|
||||
watermarkStyle.setPaint(LiPaint.solidColor(Color.from(0x4f000000, EnumColorFormat.CF_INT_ARGB)));
|
||||
ImmediateFontRenderer.drawString(this.display.getWidth() / 2.0f, 5, "PlutoEngine demo build licensed the MIT license...", BasicApplicationDemoMod.font, watermarkStyle);
|
||||
|
||||
RectangleRenderer2D.draw()
|
||||
.at(this.display.getWidth() / 2.0f - 150, this.display.getHeight() / 2.0f - 246, 300, 300)
|
||||
.recolor(0.5f, 0.5f, 0.5f, 1.0f)
|
||||
.texture(BasicApplicationDemoMod.plutoLogo).flush();
|
||||
|
||||
RectangleRenderer2D.draw()
|
||||
.at(this.display.getWidth() / 2.0f - 150, this.display.getHeight() / 2.0f - 250, 300, 300)
|
||||
.texture(BasicApplicationDemoMod.plutoLogo).flush();
|
||||
|
||||
var welcomeStyle = new TextStyleOptions(60.0f)
|
||||
.setHorizontalAlign(TextStyleOptions.TextAlign.CENTER)
|
||||
.setVerticalAlign(TextStyleOptions.TextAlign.CENTER)
|
||||
.setOverflowX(TextStyleOptions.OverflowXStrategy.SCALE_TO_FIT)
|
||||
.setFitBox(new Rectanglef(50.0f, 50.0f, this.display.getWidth() - 50.0f, this.display.getHeight() - 50.0f))
|
||||
.setPaint(LiPaint.solidColor(Color.VERY_DARK_GRAY));
|
||||
ImmediateFontRenderer.drawString(0, 102, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle);
|
||||
float gPos = (System.currentTimeMillis() % 7200) / 20.0f;
|
||||
ImmediateFontRenderer.drawString(500, 50, "Hue: %f".formatted(gPos), BasicApplicationDemoMod.font, style);
|
||||
|
||||
var stops = new LiGradientPaint.Stop[16];
|
||||
for (int i = 0; i < stops.length; i++)
|
||||
stops[i] = new LiGradientPaint.Stop(i / (float) stops.length, Color.from(new HSB(gPos + i * 10, 1.0f, 1.0f).toRGBA()));
|
||||
|
||||
welcomeStyle.setPaint(LiPaint.horizontaLinearGradient(stops));
|
||||
ImmediateFontRenderer.drawString(0, 100, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle);
|
||||
|
||||
if (InputBus.Keyboard.pressed(GLFW.GLFW_KEY_R))
|
||||
{
|
||||
|
@ -64,90 +107,8 @@ public class Main extends PlutoApplication
|
|||
shader.stop();
|
||||
shader.close();
|
||||
PlutoGUIMod.fontShader = newShader;
|
||||
|
||||
newShader.start();
|
||||
newShader.recolor.load(1, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var va = Renderer2D.standardQuad;
|
||||
va.bind();
|
||||
va.enableAllAttributes();
|
||||
|
||||
var font = BasicApplicationDemoMod.font;
|
||||
var tex = font.getAtlas();
|
||||
|
||||
tex.bind();
|
||||
|
||||
var shader = PlutoGUIMod.fontShader;
|
||||
|
||||
shader.start();
|
||||
shader.recolor.load(1, 1, 1, 1);
|
||||
shader.italic.load(false);
|
||||
shader.page.load(0);
|
||||
shader.uvBase.load(0.0f, 0.0f);
|
||||
shader.uvDelta.load(1.0f, 1.0f);
|
||||
|
||||
for (int i = 0; i < tex.getDepth(); i++)
|
||||
{
|
||||
var padding = 8;
|
||||
var x = i % 3;
|
||||
var y = i / 3;
|
||||
var size = this.size;
|
||||
|
||||
shader.transformationMatrix.load(TransformationMatrix.create(20 + x * (size + padding), 20 + y * (size + padding), 0,
|
||||
0, 0, 0,
|
||||
size, size, 1));
|
||||
|
||||
shader.page.load(i);
|
||||
va.draw(DrawMode.TRIANGLES);
|
||||
}
|
||||
|
||||
this.size += InputBus.Mouse.getScrollY() * 20;
|
||||
|
||||
if (InputBus.Keyboard.pressed(GLFW.GLFW_KEY_R))
|
||||
{
|
||||
var newShader = new RenderShaderBuilder(
|
||||
PlutoGUIMod.instance.getResource("shaders.VertexFontShader#glsl"),
|
||||
PlutoGUIMod.instance.getResource("shaders.FragmentFontShader#glsl")
|
||||
).build(FontShader.class, false);
|
||||
|
||||
if (newShader != null)
|
||||
{
|
||||
shader.stop();
|
||||
shader.close();
|
||||
PlutoGUIMod.fontShader = newShader;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
var buildStr = String.format("Build %s", VersionInfo.GAME_BUILD);
|
||||
var strWidth = FontHelper.calcStringWidth(buildStr, "default", 0.75f);
|
||||
FontRenderer.drawString(this.display.getWidth() - strWidth + 1, 3, buildStr, 0, 0, 0, 1, 0.75f, true);
|
||||
FontRenderer.drawString(this.display.getWidth() - strWidth, 2, buildStr, 0.7f, 0.7f, 0.7f, 1, 0.75f, false);
|
||||
|
||||
var fpsStr = String.format("%d FPS", Framerate.getInterpolatedFPS());
|
||||
FontRenderer.drawString(3, 3, fpsStr, 0, 0, 0, 1, 0.75f, true);
|
||||
FontRenderer.drawString(2, 2, fpsStr, 0.13f, 0.75f, 0.62f, 1, 0.75f, false);
|
||||
|
||||
var mods = PlutoLocal.components().getComponent(ModLoader.class).getAllMods();
|
||||
int modNr = 0;
|
||||
|
||||
for (var mod : mods)
|
||||
{
|
||||
var modManifest = mod.getManifest();
|
||||
var modStr = String.format("%s &c[0xff999999]&i1%s", modManifest.displayName(), mod.getVersion());
|
||||
|
||||
FontRenderer.drawString(8, 50 + modNr * 18, modStr, 0, 0, 0, 0, 0.7f, "default", true);
|
||||
FontRenderer.drawString(7, 49 + modNr * 18, modStr, 1, 1, 1, 1, 0.7f, "default", false);
|
||||
|
||||
modNr++;
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
public static Display getDisplay()
|
||||
|
|
Loading…
Reference in New Issue