Basic font rendering working.

This commit is contained in:
Harrison Deng 2020-07-02 13:13:04 -05:00
parent 56eca1b0d6
commit a881f1a086
18 changed files with 421 additions and 56 deletions

View File

@ -177,6 +177,7 @@ namespace SlatedGameToolkit.Framework {
} }
} catch (Exception e) { } catch (Exception e) {
Logger.Fatal(e.ToString()); Logger.Fatal(e.ToString());
throw e;
} finally { } finally {
stopped = true; stopped = true;
manager.Dispose(); manager.Dispose();

View File

@ -13,6 +13,8 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
public class GLContext : IDisposable public class GLContext : IDisposable
{ {
#region OpenGLFunctions
public IntPtr Handle { get; private set; } public IntPtr Handle { get; private set; }
private bool disposed; private bool disposed;
@ -224,6 +226,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
public void Enable(EnableCap cap) public void Enable(EnableCap cap)
{ {
glEnable.Invoke(cap); glEnable.Invoke(cap);
DetectGLError();
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -251,6 +254,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
public void BlendFunc(BlendingFactor sfactor, BlendingFactor dfactor) public void BlendFunc(BlendingFactor sfactor, BlendingFactor dfactor)
{ {
glBlendFunc.Invoke(sfactor, dfactor); glBlendFunc.Invoke(sfactor, dfactor);
DetectGLError();
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -305,6 +309,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
public void PixelStorei(PixelStoreParameter pname, int param) public void PixelStorei(PixelStoreParameter pname, int param)
{ {
glPixelStorei.Invoke(pname, param); glPixelStorei.Invoke(pname, param);
DetectGLError();
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -362,12 +367,13 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void PFNGLGETINTEGERVPROC(GetPName pname, out int data); private delegate void PFNGLGETINTEGERVPROC(GetPName pname, int[] data);
private PFNGLGETINTEGERVPROC glGetIntegerv; private PFNGLGETINTEGERVPROC glGetIntegerv;
public void GetIntegerv(GetPName pname, out int data) public void GetIntegerv(GetPName pname, int[] data)
{ {
glGetIntegerv.Invoke(pname, out data); glGetIntegerv.Invoke(pname, data);
DetectGLError();
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -530,6 +536,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
public void TexSubImage2D(TextureTarget target, int level, int xoffset, int yoffset, int width, int height, PixelFormat format, PixelType type, IntPtr pixels) public void TexSubImage2D(TextureTarget target, int level, int xoffset, int yoffset, int width, int height, PixelFormat format, PixelType type, IntPtr pixels)
{ {
glTexSubImage2D.Invoke(target, level, xoffset, yoffset, width, height, format, type, pixels); glTexSubImage2D.Invoke(target, level, xoffset, yoffset, width, height, format, type, pixels);
DetectGLError();
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -3153,6 +3160,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
{ {
glSampleMaski.Invoke(maskNumber, mask); glSampleMaski.Invoke(maskNumber, mask);
} }
#endregion
public GLContext(IntPtr windowHandle) public GLContext(IntPtr windowHandle)
{ {
@ -3162,6 +3170,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
throw new FrameworkSDLException(); throw new FrameworkSDLException();
} }
#region OpenGLDelegateAssignment
glCullFace = Marshal.GetDelegateForFunctionPointer<PFNGLCULLFACEPROC>(loader.Invoke("glCullFace")); glCullFace = Marshal.GetDelegateForFunctionPointer<PFNGLCULLFACEPROC>(loader.Invoke("glCullFace"));
glFrontFace = Marshal.GetDelegateForFunctionPointer<PFNGLFRONTFACEPROC>(loader.Invoke("glFrontFace")); glFrontFace = Marshal.GetDelegateForFunctionPointer<PFNGLFRONTFACEPROC>(loader.Invoke("glFrontFace"));
glHint = Marshal.GetDelegateForFunctionPointer<PFNGLHINTPROC>(loader.Invoke("glHint")); glHint = Marshal.GetDelegateForFunctionPointer<PFNGLHINTPROC>(loader.Invoke("glHint"));
@ -3506,19 +3515,29 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL
glTexImage3DMultisample = Marshal.GetDelegateForFunctionPointer<PFNGLTEXIMAGE3DMULTISAMPLEPROC>(loader.Invoke("glTexImage3DMultisample")); glTexImage3DMultisample = Marshal.GetDelegateForFunctionPointer<PFNGLTEXIMAGE3DMULTISAMPLEPROC>(loader.Invoke("glTexImage3DMultisample"));
glGetMultisamplefv = Marshal.GetDelegateForFunctionPointer<PFNGLGETMULTISAMPLEFVPROC>(loader.Invoke("glGetMultisamplefv")); glGetMultisamplefv = Marshal.GetDelegateForFunctionPointer<PFNGLGETMULTISAMPLEFVPROC>(loader.Invoke("glGetMultisamplefv"));
glSampleMaski = Marshal.GetDelegateForFunctionPointer<PFNGLSAMPLEMASKIPROC>(loader.Invoke("glSampleMaski")); glSampleMaski = Marshal.GetDelegateForFunctionPointer<PFNGLSAMPLEMASKIPROC>(loader.Invoke("glSampleMaski"));
#endregion
} }
/// <summary> /// <summary>
/// Checks for any issues in this OpenGL Context and throws an exception if it detects one. /// Checks for any issues in this OpenGL Context and throws an exception if it detects one.
/// May throw OpenGLException. /// May throw OpenGLException.
/// </summary> /// </summary>
private void DetectGLError() { public void DetectGLError() {
OpenGL.ErrorCode code = GetError(); OpenGL.ErrorCode code = GetError();
if (code != OpenGL.ErrorCode.NoError) { if (code != OpenGL.ErrorCode.NoError) {
throw new OpenGLErrorException(code); throw new OpenGLErrorException(code);
} }
} }
public void GetViewport(out int x, out int y, out int width, out int height) {
int[] viewport = new int[4];
GetIntegerv(GetPName.Viewport, viewport);
x = viewport[0];
y = viewport[1];
width = viewport[2];
height = viewport[3];
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!disposed) if (!disposed)

View File

@ -32,5 +32,6 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
/// </summary> /// </summary>
/// <value>A color for this mesh.</value> /// <value>A color for this mesh.</value>
Color Color { get; } Color Color { get; }
} }
} }

View File

@ -15,7 +15,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; private int projALoc, viewALoc, modelALoc, texturedALoc, singleChanneledALoc;
private Camera camera; private Camera camera;
private RenderProgram renderProgram; private RenderProgram renderProgram;
private ITexture texture; private ITexture texture;
@ -54,6 +54,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
viewALoc = GLContext.GetUniformLocation(renderProgram.Handle, "view"); viewALoc = GLContext.GetUniformLocation(renderProgram.Handle, "view");
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");
vertexBuffers.defineVertexAttributes(definitions: definitions); vertexBuffers.defineVertexAttributes(definitions: definitions);
GLContext.UniformMatrix4fv(projALoc, 1, false, camera.ProjectionMatrix.ToColumnMajorArray()); GLContext.UniformMatrix4fv(projALoc, 1, false, camera.ProjectionMatrix.ToColumnMajorArray());
@ -80,15 +81,18 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
IMoveable moveable = mesh as IMoveable; IMoveable moveable = mesh as IMoveable;
if (moveable != null) { if (moveable != null) {
if (GameEngine.UpdatesPerSecond <= 0) { if (GameEngine.UpdatesPerSecond <= 0) {
camera.Position = camera.MoveTo; moveable.Position = moveable.MoveTo;
} else { } else {
camera.Position = camera.Position + (camera.MoveTo - camera.Position) * batchDelta; moveable.Position = moveable.Position + (moveable.MoveTo - moveable.Position) * batchDelta;
} }
} }
if (mesh.Texture?.Handle != this.texture?.Handle) { if (mesh.Texture?.Handle != this.texture?.Handle) {
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);
}
} }
ValueTuple<Vector3, Vector2>[] vertices = mesh.Vertices; ValueTuple<Vector3, Vector2>[] vertices = mesh.Vertices;
uint[] indices = mesh.Elements; uint[] indices = mesh.Elements;
@ -134,7 +138,9 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
} }
protected virtual void Flush() { protected virtual void Flush() {
texture?.Use(); if (texture != null) {
GLContext.BindTexture(TextureTarget.Texture2D, texture.Handle);
}
renderProgram.Use(); renderProgram.Use();
if (GameEngine.UpdatesPerSecond <= 0) { if (GameEngine.UpdatesPerSecond <= 0) {
camera.Position = camera.MoveTo; camera.Position = camera.MoveTo;

View File

@ -11,8 +11,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
private bool changed; private bool changed;
private Vector3 rotation; private Vector3 rotation;
private Vector2 origin, dimensions; private Vector2 origin, dimensions;
public readonly Vector2[] textureCoords = new Vector2[4]; private ValueTuple<Vector3, Vector2>[] vertices = new ValueTuple<Vector3, Vector2>[4];
public ValueTuple<Vector3, Vector2>[] vertices = new ValueTuple<Vector3, Vector2>[4];
private uint[] indices = new uint[] {0, 1, 3, 1, 2, 3}; private uint[] indices = new uint[] {0, 1, 3, 1, 2, 3};
public ValueTuple<Vector3, Vector2>[] Vertices public ValueTuple<Vector3, Vector2>[] Vertices
@ -78,6 +77,19 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
} }
} }
public RectangleF Bounds {
get {
return new RectangleF(X, Y, Width, Height);
}
set {
X = value.X;
Y = value.Y;
Width = value.Width;
Height = value.Height;
}
}
public uint[] Elements { get {return indices; } } public uint[] Elements { get {return indices; } }
public Vector3 Rotation public Vector3 Rotation
@ -99,24 +111,33 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
public Color Color { get; set; } public Color Color { get; set; }
public RectangleMesh(ITexture texture, Color color) public RectangleMesh(RectangleF textureRegion, ITexture texture, Color color)
{ {
this.Texture = texture; this.Texture = texture;
this.Color = color; this.Color = color;
this.textureCoords[0].X = 0; this.vertices[0].Item2.X = textureRegion.X;
this.textureCoords[0].Y = 1; this.vertices[0].Item2.Y = textureRegion.Y;
this.textureCoords[1].X = 1; this.vertices[1].Item2.X = textureRegion.X + textureRegion.Width;
this.textureCoords[1].Y = 1; this.vertices[1].Item2.Y = textureRegion.Y;
this.textureCoords[2].X = 1; this.vertices[2].Item2.X = textureRegion.X + textureRegion.Width;
this.textureCoords[2].Y = 0; this.vertices[2].Item2.Y = textureRegion.Y + textureRegion.Height;
this.textureCoords[3].X = 0; this.vertices[3].Item2.X = textureRegion.X;
this.textureCoords[3].Y = 0; this.vertices[3].Item2.Y = textureRegion.Y + textureRegion.Height;
} }
public RectangleMesh(RectangleF meshBounds, RectangleF textureRegion, ITexture texture, Color color) : this(textureRegion, texture, color) {
this.Bounds = meshBounds;
}
public RectangleMesh(ITexture texture, Color color) : this(new Rectangle(0, 0, 1, 1), texture, color) {
}
private void CalculateVertices() { private void CalculateVertices() {
if (!changed) return; if (!changed) return;
@ -128,7 +149,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
vertices[i] = (Vector3.Transform(baseVerts[i], matRot), textureCoords[i]); vertices[i].Item1 = Vector3.Transform(baseVerts[i], matRot);
} }
changed = false; changed = false;
} }

View File

@ -0,0 +1,272 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Render;
using SlatedGameToolkit.Framework.Graphics.Textures;
using SlatedGameToolkit.Framework.Graphics.Window;
using SlatedGameToolkit.Framework.Utilities;
using SlatedGameToolkit.Framework.Utilities.Collections;
using StbTrueTypeSharp;
namespace SlatedGameToolkit.Framework.Graphics.Text
{
public class BitmapFont : IDisposable
{
private StbTrueType.stbtt_fontinfo info;
private byte[] fontData;
private bool disposed;
private float scale;
private int bitmapLength;
private int pixelHeight;
public int PixelHeight {
get {
return pixelHeight;
}
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);
}
info = new StbTrueType.stbtt_fontinfo();
fixed (byte* data = &fontData[0]) {
StbTrueType.stbtt_InitFont(info, data, 0);
}
PixelHeight = 64;
}
public unsafe BitmapFont(string path, GLContext glContext = null, int cacheSize = 1024, int bitmapLength = 512, int numberOfTextures = 2, params char[] initialCharacters) :
this(File.ReadAllBytes(path), glContext, cacheSize, bitmapLength, numberOfTextures, initialCharacters)
{
}
/// <summary>
/// Loads the requested character.
/// </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 bool LoadGlyph(char character) {
if (!(bitmapLocations.ContainsKey((scale, character)) && textureBuffers[bitmapLocations[(scale, character)]].Contains(scale, character))) {
bitmapLocations.Remove((scale, character));
int glyphIndex = glyphIndexCache.ComputeIfNonExistent(character, (c) => StbTrueType.stbtt_FindGlyphIndex(info, c));
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
StbTrueType.stbtt_GetGlyphBitmapBox(info, glyphIndex, scale, scale, &x0, &y0, &x1, &y1);
if (!textureBuffers[drawingTo].CanAdd(x1 - x0, y1 - y0)) {
return false;
}
textureBuffers[drawingTo].Upload(character, x1 - x0, y1 - y0, scale, info, glyphIndex);
bitmapLocations.Add((scale, character), drawingTo);
}
return true;
}
public unsafe void Draw(MeshBatch batch, double delta, float x, float y, string characters, Color color) {
float horizontal = x;
foreach (char c in characters)
{
int used = 0;
while (!LoadGlyph(c)) {
used++;
drawingTo++;
if (drawingTo > textureBuffers.Length) {
drawingTo = 0;
}
if (used >= textureBuffers.Length) {
batch.End();
batch.Begin(Matrix4x4.Identity, delta);
used = 0;
}
}
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() {
return textureBuffers[drawingTo];
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
}
foreach (FontBitmap bitmap in textureBuffers)
{
bitmap.Dispose();
}
disposed = true;
}
}
~BitmapFont()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
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);
}
}
}
}

View File

@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace SlatedGameToolkit.Framework.Graphics.Text
{
public class DynamicFont {
private readonly Dictionary<char, uint> glyphLocations = new Dictionary<char, uint>();
public DynamicFont() {
}
}
}

View File

@ -1,14 +0,0 @@
using System.Numerics;
namespace SlatedGameToolkit.Framework.Graphics.Text
{
public struct DynamicGlyph {
public readonly char character;
public readonly (Vector2, Vector2)[] vertices;
public DynamicGlyph(char character, (Vector2, Vector2)[] vertices) {
this.character = character;
this.vertices = vertices;
}
}
}

View File

@ -1,8 +1,16 @@
using System;
namespace SlatedGameToolkit.Framework.Graphics.Textures namespace SlatedGameToolkit.Framework.Graphics.Textures
{ {
public interface ITexture public interface ITexture : IDisposable
{ {
/// <summary>
/// Whether or not this model uses a texture that is single chanelled.
/// </summary>
/// <value>true for single channeled.</value>
bool SingleChanneled { get; }
uint Handle {get;} uint Handle {get;}
void Use(); uint Width {get;}
uint Height {get;}
} }
} }

View File

@ -7,13 +7,19 @@ using SlatedGameToolkit.Framework.AssetSystem;
namespace SlatedGameToolkit.Framework.Graphics.Textures namespace SlatedGameToolkit.Framework.Graphics.Textures
{ {
public class Texture : IAssetUseable, ITexture { public class Texture : IAssetUseable, ITexture {
public readonly int width, height; public readonly uint width, height;
private bool disposed; private bool disposed;
private GLContext glContext; private GLContext glContext;
private uint handle; private uint handle;
public uint Handle {get { return handle; }} public uint Handle {get { return handle; }}
public uint Width => width;
public uint Height => height;
public bool SingleChanneled => false;
/// <summary> /// <summary>
/// Creates an OpenGL Texture2D in the given GL Context. /// Creates an OpenGL Texture2D in the given GL Context.
/// </summary> /// </summary>
@ -21,8 +27,8 @@ namespace SlatedGameToolkit.Framework.Graphics.Textures
/// <param name="context">The openGL context to associate this with. If null, will use the currently active context. Defaults to null.</param> /// <param name="context">The openGL context to associate this with. If null, will use the currently active context. Defaults to null.</param>
public unsafe Texture(TextureData textureData, GLContext context = null) { public unsafe Texture(TextureData textureData, GLContext context = null) {
this.glContext = context ?? WindowContextsManager.CurrentGL; this.glContext = context ?? WindowContextsManager.CurrentGL;
this.width = textureData.width; this.width = (uint)textureData.width;
this.height = textureData.height; this.height = (uint)textureData.height;
uint[] handles = new uint[1]; uint[] handles = new uint[1];
glContext.GenTextures(1, handles); glContext.GenTextures(1, handles);
this.handle = handles[0]; this.handle = handles[0];
@ -32,6 +38,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Textures
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear); glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); glContext.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
fixed(void* p = &textureData.data[0]) { fixed(void* p = &textureData.data[0]) {
glContext.PixelStorei(PixelStoreParameter.UnpackAlignment, 4);
glContext.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)textureData.width, (uint)textureData.height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, new IntPtr(p)); glContext.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)textureData.width, (uint)textureData.height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, new IntPtr(p));
} }
glContext.GenerateMipmap(OpenGL.TextureTarget.Texture2D); glContext.GenerateMipmap(OpenGL.TextureTarget.Texture2D);

View File

@ -16,7 +16,12 @@ namespace SlatedGameToolkit.Framework.Graphics.Window
get { get {
return current; return current;
} }
set {
value.MakeCurrent();
}
} }
private static Dictionary<uint, WindowContext> existingWindows = new Dictionary<uint, WindowContext>(); private static Dictionary<uint, WindowContext> existingWindows = new Dictionary<uint, WindowContext>();
/// <summary> /// <summary>

View File

@ -4,13 +4,19 @@ out vec4 outputColor;
in vec2 texCoord; in vec2 texCoord;
in vec4 color; in vec4 color;
uniform bool singleChanneled;
uniform bool textured; uniform bool textured;
uniform sampler2D texture0; uniform sampler2D texture0;
void main() void main()
{ {
if (textured) { if (textured) {
outputColor = texture(texture0, texCoord) * color; if (singleChanneled) {
float singleVal = texture(texture0, vec2(texCoord.x, 1 - texCoord.y)).r;
outputColor = vec4(singleVal);
} else {
outputColor = texture(texture0, vec2(texCoord.x, 1 - texCoord.y)) * color;
}
} else { } else {
outputColor = color; outputColor = color;
} }

View File

@ -14,6 +14,5 @@ void main()
{ {
texCoord = aTexCoord; texCoord = aTexCoord;
color = aColor; color = aColor;
gl_Position = projection * view * models * vec4(aPosition, 1.0); gl_Position = projection * view * models * vec4(aPosition, 1.0);
} }

View File

@ -0,0 +1,16 @@
using System.Drawing;
namespace SlatedGameToolkit.Framework.Utilities
{
public static class RectangleUtils
{
public static RectangleF MultiplyBy(this RectangleF rectangle, float multiplier) {
RectangleF rect = rectangle;
rect.X *= multiplier;
rect.Y *= multiplier;
rect.Width *= multiplier;
rect.Height *= multiplier;
return rect;
}
}
}

View File

@ -10,6 +10,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.0.0.0</AssemblyVersion>
<Description>A tool to help with developing a game using SlatedGameToolkit.</Description> <Description>A tool to help with developing a game using SlatedGameToolkit.</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,14 +1,18 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using SDL2; using SDL2;
using SlatedGameToolkit.Commons.Loaders; using SlatedGameToolkit.Commons.Loaders;
using SlatedGameToolkit.Framework.Graphics.Render; using SlatedGameToolkit.Framework.Graphics.Render;
using SlatedGameToolkit.Framework.Graphics.Text;
using SlatedGameToolkit.Framework.Graphics.Textures; using SlatedGameToolkit.Framework.Graphics.Textures;
using SlatedGameToolkit.Framework.Graphics.Window; using SlatedGameToolkit.Framework.Graphics.Window;
using SlatedGameToolkit.Framework.Input.Devices; using SlatedGameToolkit.Framework.Input.Devices;
using SlatedGameToolkit.Framework.StateSystem; using SlatedGameToolkit.Framework.StateSystem;
using SlatedGameToolkit.Framework.StateSystem.States; using SlatedGameToolkit.Framework.StateSystem.States;
using StbTrueTypeSharp;
namespace SlatedGameToolkit.Tools.Utilities.Playground namespace SlatedGameToolkit.Tools.Utilities.Playground
{ {
@ -17,6 +21,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
private WindowContext window; private WindowContext window;
private Camera2D camera; private Camera2D camera;
private MeshBatch renderer; private MeshBatch renderer;
private BitmapFont font;
private Texture logoTexture, fillerTexture; private Texture logoTexture, fillerTexture;
private RectangleMesh logo, textureTester, untextured; private RectangleMesh logo, textureTester, untextured;
@ -36,6 +41,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
{ {
logoTexture.Dispose(); logoTexture.Dispose();
fillerTexture.Dispose(); fillerTexture.Dispose();
font.Dispose();
renderer.Dispose(); renderer.Dispose();
window.Dispose(); window.Dispose();
} }
@ -45,13 +51,13 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
return "main state"; return "main state";
} }
public void Initialize(StateManager manager) public unsafe void Initialize(StateManager manager)
{ {
window = new WindowContext("SlatedGameToolkit Playground"); window = new WindowContext("SlatedGameToolkit Playground");
camera = new Camera2D(2, 2); camera = new Camera2D(2, 2);
renderer = new MeshBatch(camera); renderer = new MeshBatch(camera);
logoTexture = TextureLoader.Load2DTexture("Resources/Playground/yhdnbgnc.png", null); logoTexture = TextureLoader.Load2DTexture("Resources/Playground/yhdnbgnc.png");
logo = new RectangleMesh(logoTexture, Color.White); logo = new RectangleMesh(logoTexture, Color.White);
logo.Width = 0.5f; logo.Width = 0.5f;
logo.Height = 0.5f; logo.Height = 0.5f;
@ -59,7 +65,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
logo.Y = -0.25f; logo.Y = -0.25f;
fillerTexture = TextureLoader.Load2DTexture("Resources/Playground/filler.png", null); fillerTexture = TextureLoader.Load2DTexture("Resources/Playground/filler.png");
textureTester = new RectangleMesh(fillerTexture, Color.White); textureTester = new RectangleMesh(fillerTexture, Color.White);
textureTester.Width = 0.15f; textureTester.Width = 0.15f;
textureTester.Height = 0.15f; textureTester.Height = 0.15f;
@ -70,6 +76,8 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
untextured.Height = 0.1f; untextured.Height = 0.1f;
untextured.X = 0.25f; untextured.X = 0.25f;
untextured.Y = - 0.15f; untextured.Y = - 0.15f;
font = new BitmapFont("Resources/Playground/earwig_factory_rg.ttf");
} }
public void Render(double delta) public void Render(double delta)
@ -78,6 +86,7 @@ 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, "123", Color.White);
renderer.End(); renderer.End();
} }

View File

@ -50,5 +50,25 @@ namespace SlatedGameToolkit.Framework.Tests.Utilities.Collections.Caching
cache[5] = 5; cache[5] = 5;
Assert.IsTrue(cache.ContainsKey(2)); Assert.IsTrue(cache.ContainsKey(2));
} }
[Test]
public void ComputeIfNonexistentTest() {
LRUCache<int, int> cache = new LRUCache<int, int>(4);
for (int i = 0; i < cache.MaxLength; i++) {
cache.ComputeIfNonExistent(i, (a) => a);
}
}
[Test]
public void ComputeIfNonexistentTestExists() {
LRUCache<int, int> cache = new LRUCache<int, int>(4);
for (int i = 0; i < cache.MaxLength; i++) {
cache.ComputeIfNonExistent(i, (a) => a);
}
for (int i = 0; i < cache.MaxLength; i++) {
Assert.AreEqual(i, cache.ComputeIfNonExistent(i, (a) => -a));
}
}
} }
} }