Bitmap font system works.
This commit is contained in:
parent
378712283a
commit
ad77fec3a2
@ -106,6 +106,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
|
|||||||
public void TexParameteri(TextureTarget target, TextureParameterName pname, int param)
|
public void TexParameteri(TextureTarget target, TextureParameterName pname, int param)
|
||||||
{
|
{
|
||||||
glTexParameteri.Invoke(target, pname, param);
|
glTexParameteri.Invoke(target, pname, param);
|
||||||
|
DetectGLError();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
@ -382,7 +383,9 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
|
|||||||
|
|
||||||
public IntPtr GetString(StringName name)
|
public IntPtr GetString(StringName name)
|
||||||
{
|
{
|
||||||
return glGetString.Invoke(name);
|
IntPtr res = glGetString.Invoke(name);
|
||||||
|
DetectGLError();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
@ -1459,6 +1462,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
|
|||||||
public void GetUniformfv(uint program, int location, out float parameters)
|
public void GetUniformfv(uint program, int location, out float parameters)
|
||||||
{
|
{
|
||||||
glGetUniformfv.Invoke(program, location, out parameters);
|
glGetUniformfv.Invoke(program, location, out parameters);
|
||||||
|
DetectGLError();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
@ -16,7 +16,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
private bool disposed;
|
private bool disposed;
|
||||||
private float batchDelta;
|
private float batchDelta;
|
||||||
public GLContext GLContext {get; private set; }
|
public GLContext GLContext {get; private set; }
|
||||||
private int projALoc, viewALoc, modelALoc, texturedALoc, singleChanneledALoc;
|
private int projALoc, viewALoc, modelALoc, texturedALoc, singleChanneledALoc, flippedALoc;
|
||||||
private Camera camera;
|
private Camera camera;
|
||||||
private RenderProgram renderProgram;
|
private RenderProgram renderProgram;
|
||||||
private ITexture texture;
|
private ITexture texture;
|
||||||
@ -56,6 +56,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
projALoc = GLContext.GetUniformLocation(renderProgram.Handle, "projection");
|
projALoc = GLContext.GetUniformLocation(renderProgram.Handle, "projection");
|
||||||
texturedALoc = GLContext.GetUniformLocation(renderProgram.Handle, "textured");
|
texturedALoc = GLContext.GetUniformLocation(renderProgram.Handle, "textured");
|
||||||
singleChanneledALoc = GLContext.GetUniformLocation(renderProgram.Handle, "singleChanneled");
|
singleChanneledALoc = GLContext.GetUniformLocation(renderProgram.Handle, "singleChanneled");
|
||||||
|
flippedALoc = GLContext.GetUniformLocation(renderProgram.Handle, "flipped");
|
||||||
vertexBuffers.defineVertexAttributes(definitions: definitions);
|
vertexBuffers.defineVertexAttributes(definitions: definitions);
|
||||||
|
|
||||||
GLContext.UniformMatrix4fv(projALoc, 1, false, camera.ProjectionMatrix.ToColumnMajorArray());
|
GLContext.UniformMatrix4fv(projALoc, 1, false, camera.ProjectionMatrix.ToColumnMajorArray());
|
||||||
@ -91,9 +92,10 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
Flush();
|
Flush();
|
||||||
this.texture = mesh.Texture;
|
this.texture = mesh.Texture;
|
||||||
GLContext.Uniform1i(texturedALoc, texture == null ? 0 : 1);
|
GLContext.Uniform1i(texturedALoc, texture == null ? 0 : 1);
|
||||||
if (texture != null) {
|
}
|
||||||
GLContext.Uniform1i(singleChanneledALoc, texture.SingleChanneled ? 1 : 0);
|
if (texture != null) {
|
||||||
}
|
GLContext.Uniform1i(singleChanneledALoc, texture.SingleChanneled ? 1 : 0);
|
||||||
|
GLContext.Uniform1i(flippedALoc, texture.Flipped ? 1 : 0);
|
||||||
}
|
}
|
||||||
ValueTuple<Vector3, Vector2>[] vertices = mesh.Vertices;
|
ValueTuple<Vector3, Vector2>[] vertices = mesh.Vertices;
|
||||||
uint[] indices = mesh.Elements;
|
uint[] indices = mesh.Elements;
|
||||||
@ -131,14 +133,15 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
lengths[offsetIndex] = elementsCount;
|
lengths[offsetIndex] = elementsCount;
|
||||||
offsetIndex++;
|
offsetIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void End() {
|
public virtual void End() {
|
||||||
if (!Batching) throw new InvalidOperationException("This batch was never started.");
|
if (!Batching) throw new InvalidOperationException("This batch was never started.");
|
||||||
this.Batching = false;
|
this.Batching = false;
|
||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Flush() {
|
public virtual void Flush() {
|
||||||
|
if (offsetIndex == 0) return;
|
||||||
if (texture != null) {
|
if (texture != null) {
|
||||||
GLContext.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
GLContext.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,14 @@ using SlatedGameToolkit.Framework.Graphics.Textures;
|
|||||||
|
|
||||||
namespace SlatedGameToolkit.Framework.Graphics.Render
|
namespace SlatedGameToolkit.Framework.Graphics.Render
|
||||||
{
|
{
|
||||||
public class RectangleMesh : IMesh
|
public struct RectangleMesh : IMesh
|
||||||
{
|
{
|
||||||
private Matrix4x4 matRot = Matrix4x4.Identity;
|
private Matrix4x4 matRot;
|
||||||
private bool changed;
|
private bool changed;
|
||||||
private Vector3 rotation;
|
private Vector3 rotation;
|
||||||
private Vector2 origin, dimensions;
|
private Vector2 origin, dimensions;
|
||||||
private ValueTuple<Vector3, Vector2>[] vertices = new ValueTuple<Vector3, Vector2>[4];
|
private ValueTuple<Vector3, Vector2>[] vertices;
|
||||||
private uint[] indices = new uint[] {0, 1, 3, 1, 2, 3};
|
private uint[] indices;
|
||||||
|
|
||||||
public ValueTuple<Vector3, Vector2>[] Vertices
|
public ValueTuple<Vector3, Vector2>[] Vertices
|
||||||
{
|
{
|
||||||
@ -113,9 +113,15 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
|
|
||||||
public RectangleMesh(RectangleF textureRegion, ITexture texture, Color color)
|
public RectangleMesh(RectangleF textureRegion, ITexture texture, Color color)
|
||||||
{
|
{
|
||||||
|
this.changed = true;
|
||||||
|
this.rotation = Vector3.Zero;
|
||||||
|
this.origin = Vector2.Zero;
|
||||||
|
this.dimensions = Vector2.Zero;
|
||||||
this.Texture = texture;
|
this.Texture = texture;
|
||||||
this.Color = color;
|
this.Color = color;
|
||||||
|
this.indices = new uint[] {0, 1, 3, 1, 2, 3};
|
||||||
|
vertices = new ValueTuple<Vector3, Vector2>[4];
|
||||||
|
|
||||||
this.vertices[0].Item2.X = textureRegion.X;
|
this.vertices[0].Item2.X = textureRegion.X;
|
||||||
this.vertices[0].Item2.Y = textureRegion.Y;
|
this.vertices[0].Item2.Y = textureRegion.Y;
|
||||||
|
|
||||||
@ -127,6 +133,10 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
|
|||||||
|
|
||||||
this.vertices[3].Item2.X = textureRegion.X;
|
this.vertices[3].Item2.X = textureRegion.X;
|
||||||
this.vertices[3].Item2.Y = textureRegion.Y + textureRegion.Height;
|
this.vertices[3].Item2.Y = textureRegion.Y + textureRegion.Height;
|
||||||
|
|
||||||
|
this.matRot = Matrix4x4.Identity;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectangleMesh(RectangleF meshBounds, RectangleF textureRegion, ITexture texture, Color color) : this(textureRegion, texture, color) {
|
public RectangleMesh(RectangleF meshBounds, RectangleF textureRegion, ITexture texture, Color color) : this(textureRegion, texture, color) {
|
||||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using SlatedGameToolkit.Framework.Exceptions;
|
||||||
using SlatedGameToolkit.Framework.Graphics.OpenGL;
|
using SlatedGameToolkit.Framework.Graphics.OpenGL;
|
||||||
using SlatedGameToolkit.Framework.Graphics.Render;
|
using SlatedGameToolkit.Framework.Graphics.Render;
|
||||||
using SlatedGameToolkit.Framework.Graphics.Textures;
|
using SlatedGameToolkit.Framework.Graphics.Textures;
|
||||||
@ -16,130 +16,245 @@ namespace SlatedGameToolkit.Framework.Graphics.Text
|
|||||||
{
|
{
|
||||||
public class BitmapFont : IDisposable
|
public class BitmapFont : IDisposable
|
||||||
{
|
{
|
||||||
private StbTrueType.stbtt_fontinfo info;
|
private GLContext context;
|
||||||
private byte[] fontData;
|
LRUCache<char, int> glyphIndices;
|
||||||
private bool disposed;
|
FontTexture[] textures;
|
||||||
|
StbTrueType.stbtt_fontinfo info;
|
||||||
|
private int spaceAdvance;
|
||||||
private float scale;
|
private float scale;
|
||||||
private int bitmapLength;
|
private float pixelHeight;
|
||||||
private int pixelHeight;
|
public float PixelHeight
|
||||||
public int PixelHeight {
|
{
|
||||||
get {
|
get
|
||||||
|
{
|
||||||
return pixelHeight;
|
return pixelHeight;
|
||||||
}
|
}
|
||||||
|
set
|
||||||
set {
|
|
||||||
pixelHeight = value;
|
|
||||||
scale = StbTrueType.stbtt_ScaleForPixelHeight(info, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float kerning;
|
|
||||||
public float Kerning {
|
|
||||||
get {
|
|
||||||
return kerning;
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
kerning = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private Dictionary<(float, char), int> bitmapLocations;
|
|
||||||
private LRUCache<char, int> glyphIndexCache;
|
|
||||||
private FontBitmap[] textureBuffers;
|
|
||||||
private int drawingTo = 0;
|
|
||||||
|
|
||||||
GLContext glContext;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a font with the given data.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fontData">The byte array containing all ttf data.</param>
|
|
||||||
/// <param name="glContext">The OpenGL context to use to associate with.</param>
|
|
||||||
/// <param name="cacheSize">The size of the caches.</param>
|
|
||||||
/// <param name="bitmapLength">The length of one side of a bitmap, therefore, the total would be this squared.</param>
|
|
||||||
/// <param name="numberOfTextures">The number of backing textures to use.</param>
|
|
||||||
/// <param name="initialCharacters">An initial set of characters to load.</param>
|
|
||||||
public unsafe BitmapFont(byte[] fontData, GLContext glContext = null, int cacheSize = 1024, int bitmapLength = 512, int numberOfTextures = 2, params char[] initialCharacters) {
|
|
||||||
this.glContext = glContext ?? WindowContextsManager.CurrentGL;
|
|
||||||
if (bitmapLength < 0) throw new ArgumentOutOfRangeException("bitmapLength");
|
|
||||||
if (fontData == null || fontData.Length <= 0) throw new ArgumentException("fontData must be an array of bytes representing a TTF file.");
|
|
||||||
glyphIndexCache = new LRUCache<char, int>(cacheSize);
|
|
||||||
bitmapLocations = new Dictionary<(float, char), int>();
|
|
||||||
this.fontData = fontData;
|
|
||||||
|
|
||||||
textureBuffers = new FontBitmap[numberOfTextures];
|
|
||||||
this.bitmapLength = bitmapLength;
|
|
||||||
for (int i = 0; i < textureBuffers.Length; i++)
|
|
||||||
{
|
{
|
||||||
textureBuffers[i] = new FontBitmap(this.glContext, bitmapLength);
|
this.scale = StbTrueType.stbtt_ScaleForPixelHeight(info, value);
|
||||||
|
this.pixelHeight = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
private Dictionary<(char, float), int> glyphTexLocations = new Dictionary<(char, float), int>();
|
||||||
|
private LRUCache<char, CharacterMetrics> metricsCache;
|
||||||
|
private int drawingTo;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public unsafe BitmapFont(byte[] data, GLContext glContext = null, int cacheSize = 1024, int textures = 2, uint textureSizes = 512) {
|
||||||
info = new StbTrueType.stbtt_fontinfo();
|
info = new StbTrueType.stbtt_fontinfo();
|
||||||
|
fixed(byte* dataPtr = &data[0]) {
|
||||||
fixed (byte* data = &fontData[0]) {
|
StbTrueType.stbtt_InitFont(info, dataPtr, 0);
|
||||||
StbTrueType.stbtt_InitFont(info, data, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.glyphIndices = new LRUCache<char, int>(cacheSize);
|
||||||
|
this.metricsCache = new LRUCache<char, CharacterMetrics>(cacheSize);
|
||||||
|
this.textures = new FontTexture[textures];
|
||||||
|
|
||||||
|
this.context = glContext ?? WindowContextsManager.CurrentGL;
|
||||||
|
for (int i = 0; i < this.textures.Length; i++) {
|
||||||
|
this.textures[i] = new FontTexture(info, context, glyphIndices, textureSizes);
|
||||||
|
}
|
||||||
PixelHeight = 64;
|
PixelHeight = 64;
|
||||||
|
int spaceAdvance, leftSideBearing;
|
||||||
|
StbTrueType.stbtt_GetCodepointHMetrics(info, ' ', &spaceAdvance, &leftSideBearing);
|
||||||
|
this.spaceAdvance = spaceAdvance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe BitmapFont(string path, GLContext glContext = null, int cacheSize = 1024, int bitmapLength = 512, int numberOfTextures = 2, params char[] initialCharacters) :
|
public BitmapFont(string path, GLContext glContext = null, int cacheSize = 1024, int textures = 2, uint textureSizes = 512) : this(File.ReadAllBytes(path), glContext, cacheSize, textures, textureSizes) {
|
||||||
this(File.ReadAllBytes(path), glContext, cacheSize, bitmapLength, numberOfTextures, initialCharacters)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public ITexture GetTextureBacking(int index) {
|
||||||
/// Loads the requested character.
|
return textures[index];
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="character">The character to load.</param>
|
|
||||||
/// <returns>True if already loaded or successfully loaded, false if could not load.</returns>
|
public unsafe void Draw(MeshBatch batch, float x, float baseLine, string text, Color color) {
|
||||||
public unsafe bool LoadGlyph(char character) {
|
int textureChanges = 0;
|
||||||
if (!(bitmapLocations.ContainsKey((scale, character)) && textureBuffers[bitmapLocations[(scale, character)]].Contains(scale, character))) {
|
|
||||||
bitmapLocations.Remove((scale, character));
|
float currentPoint = x;
|
||||||
int glyphIndex = glyphIndexCache.ComputeIfNonExistent(character, (c) => StbTrueType.stbtt_FindGlyphIndex(info, c));
|
char[] chars = text.ToCharArray();
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
for (int i = 0; i < chars.Length; i++) {
|
||||||
|
char c = chars[i];
|
||||||
|
int glyphIndex = glyphIndices.ComputeIfNonExistent(c, (p) => StbTrueType.stbtt_FindGlyphIndex(info, p));
|
||||||
|
CharacterMetrics metrics = metricsCache.ComputeIfNonExistent(c, (p) => new CharacterMetrics(glyphIndex, info));
|
||||||
|
int viewWidth, viewHeight, vX, vY;
|
||||||
|
context.GetViewport(out vX, out vY, out viewWidth, out viewHeight);
|
||||||
|
|
||||||
|
if (c != ' ') {
|
||||||
|
//Check if glyph is loaded, if not, load it.
|
||||||
|
if (!glyphTexLocations.ContainsKey((c, scale)) || !textures[glyphTexLocations[(c, scale)]].ContainsChar(c, scale)) {
|
||||||
|
glyphTexLocations.Remove((c, scale));
|
||||||
|
if (!textures[drawingTo].Upload(scale, c)) {
|
||||||
|
drawingTo++;
|
||||||
|
if (drawingTo >= textures.Length) drawingTo = 0;
|
||||||
|
FontTexture fontTexture = textures[drawingTo];
|
||||||
|
fontTexture.Clear();
|
||||||
|
fontTexture.Upload(scale, c);
|
||||||
|
textureChanges++;
|
||||||
|
if (textureChanges > textures.Length) throw new FrameworkUsageException(string.Format("String \"{0}\" takes up too much texture space! Consider increasing decreasing font size, or increasing texture lengths, or number of backing textures. Attempted to swap {1} times within draw call.", text, textureChanges));
|
||||||
|
}
|
||||||
|
glyphTexLocations.Add((c, scale), drawingTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
FontTexture texture = textures[glyphTexLocations[(c, scale)]];
|
||||||
|
RectangleF texBounds = texture.GetGlyphBoundaries(c, scale);
|
||||||
|
RectangleF transformedTexBounds = texBounds.MultiplyBy(1f/texture.Length);
|
||||||
|
transformedTexBounds.Y += transformedTexBounds.Height;
|
||||||
|
transformedTexBounds.Height *= -1;
|
||||||
|
RectangleF glyphBounds = new RectangleF(currentPoint, baseLine, texBounds.Width / viewWidth, texBounds.Height / viewHeight);
|
||||||
|
glyphBounds.X += (metrics.leftSideBearing * scale) / viewWidth;
|
||||||
|
glyphBounds.Y += (metrics.vertOffset * scale) / viewHeight;
|
||||||
|
|
||||||
|
batch.Draw(new RectangleMesh(glyphBounds, transformedTexBounds, texture, color));
|
||||||
|
currentPoint += (metrics.advanceWidth * scale) / viewWidth;
|
||||||
|
if (i + 1 < chars.Length) {
|
||||||
|
int nextGlyph = glyphIndices.ComputeIfNonExistent(chars[i + 1], (p) => StbTrueType.stbtt_FindGlyphIndex(info, p));
|
||||||
|
currentPoint += (StbTrueType.stbtt_GetGlyphKernAdvance(info, glyphIndex, nextGlyph) * scale) / viewWidth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPoint += (scale * spaceAdvance) / viewWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct CharacterMetrics {
|
||||||
|
public readonly int vertOffset;
|
||||||
|
public readonly int leftSideBearing;
|
||||||
|
public readonly int advanceWidth;
|
||||||
|
|
||||||
|
public unsafe CharacterMetrics(int glyphIndex, StbTrueType.stbtt_fontinfo info) {
|
||||||
|
int x0, y0, leftSideBearing, advanceWidth;
|
||||||
|
StbTrueType.stbtt_GetGlyphHMetrics(info, glyphIndex, &advanceWidth, &leftSideBearing);
|
||||||
|
StbTrueType.stbtt_GetGlyphBox(info, glyphIndex, &x0, &y0, null, null);
|
||||||
|
this.vertOffset = y0;
|
||||||
|
this.leftSideBearing = leftSideBearing;
|
||||||
|
this.advanceWidth = advanceWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FontTexture : ITexture {
|
||||||
|
private LRUCache<char, int> glyphIndices;
|
||||||
|
public uint Length {get; private set;}
|
||||||
|
StbTrueType.stbtt_fontinfo info;
|
||||||
|
GLContext context;
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public bool SingleChanneled => true;
|
||||||
|
|
||||||
|
public uint Handle {get; private set;}
|
||||||
|
|
||||||
|
public uint Width => Length;
|
||||||
|
|
||||||
|
public uint Height => Length;
|
||||||
|
|
||||||
|
public bool Flipped => false;
|
||||||
|
private Dictionary<(char, float), Rectangle> glyphBounds = new Dictionary<(char, float), Rectangle>();
|
||||||
|
private int row, column, previousRow;
|
||||||
|
|
||||||
|
public FontTexture(StbTrueType.stbtt_fontinfo info, GLContext context, LRUCache<char, int> glyphIndices, uint length) {
|
||||||
|
this.context = context;
|
||||||
|
this.Length = length;
|
||||||
|
this.info = info;
|
||||||
|
this.glyphIndices = glyphIndices;
|
||||||
|
context.DetectGLError();
|
||||||
|
uint[] handles = new uint[1];
|
||||||
|
context.GenTextures(handles.Length, handles);
|
||||||
|
Handle = handles[0];
|
||||||
|
|
||||||
|
context.BindTexture(TextureTarget.Texture2D, Handle);
|
||||||
|
context.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Red, length, length, 0, PixelFormat.Red, PixelType.UnsignedByte, IntPtr.Zero);
|
||||||
|
context.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||||
|
context.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||||
|
context.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||||
|
context.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsChar(char c, float scale) {
|
||||||
|
return glyphBounds.ContainsKey((c, scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle GetGlyphBoundaries(char c, float scale) {
|
||||||
|
return glyphBounds[(c, scale)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool Upload(float scale, char c) {
|
||||||
|
int x0, y0, x1, y1;
|
||||||
|
int glyphIndex = glyphIndices.ComputeIfNonExistent(c, (p) => StbTrueType.stbtt_FindGlyphIndex(info, p));
|
||||||
StbTrueType.stbtt_GetGlyphBitmapBox(info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1);
|
StbTrueType.stbtt_GetGlyphBitmapBox(info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1);
|
||||||
if (!textureBuffers[drawingTo].CanAdd(x1 - x0, y1 - y0)) {
|
int width = x1 - x0, height = y1 - y0;
|
||||||
return false;
|
if (width == 0 || height == 0) throw new InternalFrameworkException("Glyph width or height was 0. Character was: " + c);
|
||||||
|
if (column + width >= Length) {
|
||||||
|
column = 0;
|
||||||
|
if (row + height + 1 >= Length) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
previousRow = row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (row - height <= previousRow) {
|
||||||
textureBuffers[drawingTo].Upload(character, x1 - x0, y1 - y0, scale, info, glyphIndex);
|
row = previousRow + height + 1;
|
||||||
bitmapLocations.Add((scale, character), drawingTo);
|
}
|
||||||
}
|
column += 1 + width;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Draw(MeshBatch batch, double delta, float x, float y, string characters, Color color) {
|
context.BindTexture(TextureTarget.Texture2D, Handle);
|
||||||
float horizontal = x;
|
int alignment;
|
||||||
foreach (char c in characters)
|
int[] alignments = new int[1];
|
||||||
|
context.GetIntegerv(GetPName.UnpackAlignment, alignments);
|
||||||
|
alignment = alignments[0];
|
||||||
|
if (alignment != 1) {
|
||||||
|
context.PixelStorei(PixelStoreParameter.UnpackAlignment, 1);
|
||||||
|
}
|
||||||
|
byte[] bitmap = new byte[width * height];
|
||||||
|
fixed(byte* data = &bitmap[0]) {
|
||||||
|
StbTrueType.stbtt_MakeGlyphBitmap(info, data, width, height, width, scale, scale, glyphIndex);
|
||||||
|
context.TexSubImage2D(TextureTarget.Texture2D, 0, column - width, row - height, width, height, PixelFormat.Red, PixelType.UnsignedByte, new IntPtr(data));
|
||||||
|
}
|
||||||
|
if (alignment != 1) {
|
||||||
|
context.PixelStorei(PixelStoreParameter.UnpackAlignment, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
glyphBounds.Add((c, scale), new Rectangle(column - width, row - height, width, height));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanFit(int width, int height) {
|
||||||
|
if (row + height < Length) {
|
||||||
|
return true;
|
||||||
|
} else if (column + width < Length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() {
|
||||||
|
glyphBounds.Clear();
|
||||||
|
row = 0;
|
||||||
|
column = 0;
|
||||||
|
previousRow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
int used = 0;
|
if (!disposed)
|
||||||
while (!LoadGlyph(c)) {
|
{
|
||||||
used++;
|
if (disposing)
|
||||||
drawingTo++;
|
{
|
||||||
if (drawingTo > textureBuffers.Length) {
|
|
||||||
drawingTo = 0;
|
|
||||||
}
|
|
||||||
if (used >= textureBuffers.Length) {
|
|
||||||
batch.End();
|
|
||||||
batch.Begin(Matrix4x4.Identity, delta);
|
|
||||||
used = 0;
|
|
||||||
}
|
}
|
||||||
|
context.DeleteTextures(1, new uint[] {Handle});
|
||||||
|
disposed = true;
|
||||||
}
|
}
|
||||||
ITexture texture = textureBuffers[bitmapLocations[(scale, c)]];
|
|
||||||
RectangleF glyphBounds = textureBuffers[bitmapLocations[(scale, c)]].GetGlyphTextureBounds(scale, c);
|
|
||||||
int width, height, vX, vY;
|
|
||||||
glContext.GetViewport(out vX, out vY, out width, out height);
|
|
||||||
float glyphWidth = glyphBounds.Width / width;
|
|
||||||
float glyphHeight = glyphBounds.Height / height;
|
|
||||||
RectangleMesh mesh = new RectangleMesh(new RectangleF(horizontal, y, glyphWidth, glyphHeight), glyphBounds.MultiplyBy(1f/bitmapLength), texture, color);
|
|
||||||
horizontal += (glyphBounds.Width - kerning) / width;
|
|
||||||
batch.Draw(mesh);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public ITexture GetActiveTextureBacking() {
|
~FontTexture()
|
||||||
return textureBuffers[drawingTo];
|
{
|
||||||
|
Dispose(disposing: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
System.GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
@ -148,10 +263,11 @@ namespace SlatedGameToolkit.Framework.Graphics.Text
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
// TODO: dispose managed state (managed objects)
|
||||||
}
|
}
|
||||||
foreach (FontBitmap bitmap in textureBuffers)
|
foreach (FontTexture texture in this.textures)
|
||||||
{
|
{
|
||||||
bitmap.Dispose();
|
texture.Dispose();
|
||||||
}
|
}
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
@ -167,106 +283,5 @@ namespace SlatedGameToolkit.Framework.Graphics.Text
|
|||||||
Dispose(disposing: true);
|
Dispose(disposing: true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
private class FontBitmap : ITexture
|
|
||||||
{
|
|
||||||
private GLContext glContext;
|
|
||||||
private bool disposed;
|
|
||||||
private int bitmapLength;
|
|
||||||
private int lastRow;
|
|
||||||
private Point bitmapPosition;
|
|
||||||
private Dictionary<(float, char), RectangleF> glyphBitmaps = new Dictionary<(float, char), RectangleF>();
|
|
||||||
public uint Handle {get; private set;}
|
|
||||||
|
|
||||||
public uint Width {get; private set;}
|
|
||||||
|
|
||||||
public uint Height {get; private set;}
|
|
||||||
|
|
||||||
public bool SingleChanneled => true;
|
|
||||||
|
|
||||||
public unsafe FontBitmap(GLContext glContext, int length) {
|
|
||||||
uint[] handles = new uint[1];
|
|
||||||
this.glContext = glContext;
|
|
||||||
glContext.GenTextures(handles.Length, handles);
|
|
||||||
Handle = handles[0];
|
|
||||||
glContext.BindTexture(TextureTarget.Texture2D, Handle);
|
|
||||||
glContext.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Red, (uint)length, (uint)length, 0, PixelFormat.Red, PixelType.UnsignedByte, IntPtr.Zero);
|
|
||||||
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
|
||||||
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
|
||||||
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
|
||||||
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
|
||||||
this.bitmapLength = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(float scale, char character) {
|
|
||||||
return glyphBitmaps.ContainsKey((scale, character));
|
|
||||||
}
|
|
||||||
|
|
||||||
public RectangleF GetGlyphTextureBounds(float scale, char character) {
|
|
||||||
return glyphBitmaps[(scale, character)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Upload(char character, int width, int height, float scale, StbTrueType.stbtt_fontinfo info, int glyphIndex) {
|
|
||||||
if (bitmapPosition.X + width < bitmapLength) { // Within the first row
|
|
||||||
if (bitmapPosition.Y - lastRow < height) {
|
|
||||||
bitmapPosition.Y = lastRow + height + 1;
|
|
||||||
if (bitmapPosition.Y >= bitmapLength) {
|
|
||||||
bitmapPosition = Point.Empty;
|
|
||||||
glyphBitmaps.Clear();
|
|
||||||
}
|
|
||||||
} // the prev row + the current bitmaps height will be the maximum height.
|
|
||||||
bitmapPosition.X += width + 1; // move the x position.
|
|
||||||
glContext.BindTexture(TextureTarget.Texture2D, Handle);
|
|
||||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
|
||||||
byte* dataPtr = StbTrueType.stbtt_GetGlyphBitmap(info, scale, scale, glyphIndex, &w, &h, &xoff, &yoff);
|
|
||||||
IntPtr data = new IntPtr(dataPtr);
|
|
||||||
Rectangle drawLocation = new Rectangle(bitmapPosition.X - width, bitmapPosition.Y - height, width, height);
|
|
||||||
int prevAlignment = 0;
|
|
||||||
int[] prevAlignArr = new int[1];
|
|
||||||
glContext.GetIntegerv(GetPName.UnpackAlignment, prevAlignArr);
|
|
||||||
prevAlignment = prevAlignArr[0];
|
|
||||||
if (prevAlignment != 1) {
|
|
||||||
glContext.PixelStorei(PixelStoreParameter.UnpackAlignment, 1);
|
|
||||||
}
|
|
||||||
glContext.TexSubImage2D(TextureTarget.Texture2D, 0, drawLocation.X, drawLocation.Y, drawLocation.Width, drawLocation.Height, PixelFormat.Red, PixelType.UnsignedByte, data);
|
|
||||||
if (prevAlignment != 1) {
|
|
||||||
glContext.PixelStorei(PixelStoreParameter.UnpackAlignment, prevAlignment);
|
|
||||||
}
|
|
||||||
StbTrueType.stbtt_FreeBitmap(dataPtr, null);
|
|
||||||
glyphBitmaps.Add((scale, character), new Rectangle(drawLocation.X, bitmapLength - drawLocation.Y - drawLocation.Height, drawLocation.Width, drawLocation.Height));
|
|
||||||
|
|
||||||
} else { // Next row
|
|
||||||
lastRow = bitmapPosition.Y; // sets the previous row var to the previous row.
|
|
||||||
bitmapPosition.X = 0;
|
|
||||||
Upload(character, width, height, scale, info, glyphIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanAdd(int width, int height) {
|
|
||||||
return (bitmapPosition.X + width < bitmapLength) && (bitmapPosition.Y + height < bitmapLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposed)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
glContext.DeleteTextures(1, new uint[] {Handle});
|
|
||||||
disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~FontBitmap()
|
|
||||||
{
|
|
||||||
Dispose(disposing: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Textures
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>true for single channeled.</value>
|
/// <value>true for single channeled.</value>
|
||||||
bool SingleChanneled { get; }
|
bool SingleChanneled { get; }
|
||||||
|
bool Flipped { get; }
|
||||||
uint Handle {get;}
|
uint Handle {get;}
|
||||||
uint Width {get;}
|
uint Width {get;}
|
||||||
uint Height {get;}
|
uint Height {get;}
|
||||||
|
@ -20,6 +20,8 @@ namespace SlatedGameToolkit.Framework.Graphics.Textures
|
|||||||
|
|
||||||
public bool SingleChanneled => false;
|
public bool SingleChanneled => false;
|
||||||
|
|
||||||
|
public bool Flipped => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an OpenGL Texture2D in the given GL Context.
|
/// Creates an OpenGL Texture2D in the given GL Context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -5,16 +5,21 @@ in vec2 texCoord;
|
|||||||
in vec4 color;
|
in vec4 color;
|
||||||
|
|
||||||
uniform bool singleChanneled;
|
uniform bool singleChanneled;
|
||||||
|
uniform bool flipped;
|
||||||
uniform bool textured;
|
uniform bool textured;
|
||||||
uniform sampler2D texture0;
|
uniform sampler2D texture0;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
float yVal = texCoord.y;
|
||||||
|
if (flipped) {
|
||||||
|
yVal = 1 - texCoord.y;
|
||||||
|
}
|
||||||
if (textured) {
|
if (textured) {
|
||||||
if (singleChanneled) {
|
if (singleChanneled) {
|
||||||
outputColor = vec4(color.xyz, texture(texture0, vec2(texCoord.x, 1 - texCoord.y)).r);
|
outputColor = vec4(color.xyz, texture(texture0, vec2(texCoord.x, yVal)).r);
|
||||||
} else {
|
} else {
|
||||||
outputColor = texture(texture0, vec2(texCoord.x, 1 - texCoord.y)) * color;
|
outputColor = texture(texture0, vec2(texCoord.x, yVal)) * color;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outputColor = color;
|
outputColor = color;
|
||||||
|
@ -24,6 +24,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
|
|||||||
private BitmapFont font;
|
private BitmapFont font;
|
||||||
private Texture logoTexture, fillerTexture;
|
private Texture logoTexture, fillerTexture;
|
||||||
private RectangleMesh logo, textureTester, untextured;
|
private RectangleMesh logo, textureTester, untextured;
|
||||||
|
private string lastPressed;
|
||||||
|
|
||||||
public WindowContext CurrentWindow { get { return window;}}
|
public WindowContext CurrentWindow { get { return window;}}
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
|
|||||||
untextured.Y = - 0.15f;
|
untextured.Y = - 0.15f;
|
||||||
|
|
||||||
font = new BitmapFont("Resources/Playground/earwig_factory_rg.ttf");
|
font = new BitmapFont("Resources/Playground/earwig_factory_rg.ttf");
|
||||||
|
font.PixelHeight = 128;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(double delta)
|
public void Render(double delta)
|
||||||
@ -86,8 +88,8 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
|
|||||||
renderer.Draw(logo);
|
renderer.Draw(logo);
|
||||||
renderer.Draw(textureTester);
|
renderer.Draw(textureTester);
|
||||||
renderer.Draw(untextured);
|
renderer.Draw(untextured);
|
||||||
font.Draw(renderer, delta, 0.25f, -0.25f, "1234", Color.White);
|
font.Draw(renderer, 0.25f, -0.35f, "ABCDEFHIJKLMNOPQRSTUVWXYZ1234567890", Color.White);
|
||||||
font.Draw(renderer, delta, 0.25f, -0.35f, "abcd", Color.White);
|
renderer.Draw(new RectangleMesh(new RectangleF(-1, -1, 0.5f, 0.5f), new RectangleF(0, 1, 1, -1), font.GetTextureBacking(0), Color.White));
|
||||||
renderer.End();
|
renderer.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,5 +108,9 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
|
|||||||
camera.MoveTo += new Vector2((0.25f) * (float) timeStep, 0);
|
camera.MoveTo += new Vector2((0.25f) * (float) timeStep, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void KeyPressed(SDL.SDL_Keycode keycode, bool pressed) {
|
||||||
|
if (pressed) lastPressed = SDL.SDL_GetKeyName(keycode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user