diff --git a/src/SlatedGameToolkit.Framework/GameEngine.cs b/src/SlatedGameToolkit.Framework/GameEngine.cs index 8307488..8422539 100644 --- a/src/SlatedGameToolkit.Framework/GameEngine.cs +++ b/src/SlatedGameToolkit.Framework/GameEngine.cs @@ -177,6 +177,7 @@ namespace SlatedGameToolkit.Framework { } } catch (Exception e) { Logger.Fatal(e.ToString()); + throw e; } finally { stopped = true; manager.Dispose(); diff --git a/src/SlatedGameToolkit.Framework/Graphics/OpenGL/GLContext.cs b/src/SlatedGameToolkit.Framework/Graphics/OpenGL/GLContext.cs index 588b8f8..643197c 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/OpenGL/GLContext.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/OpenGL/GLContext.cs @@ -13,6 +13,8 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL [SuppressMessage("ReSharper", "InconsistentNaming")] public class GLContext : IDisposable { + #region OpenGLFunctions + public IntPtr Handle { get; private set; } private bool disposed; @@ -224,6 +226,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL public void Enable(EnableCap cap) { glEnable.Invoke(cap); + DetectGLError(); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -251,6 +254,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL public void BlendFunc(BlendingFactor sfactor, BlendingFactor dfactor) { glBlendFunc.Invoke(sfactor, dfactor); + DetectGLError(); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -305,6 +309,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL public void PixelStorei(PixelStoreParameter pname, int param) { glPixelStorei.Invoke(pname, param); + DetectGLError(); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -362,12 +367,13 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void PFNGLGETINTEGERVPROC(GetPName pname, out int data); + private delegate void PFNGLGETINTEGERVPROC(GetPName pname, int[] data); 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)] @@ -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) { glTexSubImage2D.Invoke(target, level, xoffset, yoffset, width, height, format, type, pixels); + DetectGLError(); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -3153,6 +3160,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL { glSampleMaski.Invoke(maskNumber, mask); } + #endregion public GLContext(IntPtr windowHandle) { @@ -3162,6 +3170,7 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL throw new FrameworkSDLException(); } + #region OpenGLDelegateAssignment glCullFace = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glCullFace")); glFrontFace = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glFrontFace")); glHint = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glHint")); @@ -3506,19 +3515,29 @@ namespace SlatedGameToolkit.Framework.Graphics.OpenGL glTexImage3DMultisample = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glTexImage3DMultisample")); glGetMultisamplefv = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glGetMultisamplefv")); glSampleMaski = Marshal.GetDelegateForFunctionPointer(loader.Invoke("glSampleMaski")); + #endregion } /// /// Checks for any issues in this OpenGL Context and throws an exception if it detects one. /// May throw OpenGLException. /// - private void DetectGLError() { + public void DetectGLError() { OpenGL.ErrorCode code = GetError(); if (code != OpenGL.ErrorCode.NoError) { 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) { if (!disposed) diff --git a/src/SlatedGameToolkit.Framework/Graphics/Render/IMesh.cs b/src/SlatedGameToolkit.Framework/Graphics/Render/IMesh.cs index 2e2c51b..3ffed11 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Render/IMesh.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Render/IMesh.cs @@ -32,5 +32,6 @@ namespace SlatedGameToolkit.Framework.Graphics.Render /// /// A color for this mesh. Color Color { get; } + } } \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Render/MeshBatch.cs b/src/SlatedGameToolkit.Framework/Graphics/Render/MeshBatch.cs index 0ebe499..c20a8eb 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Render/MeshBatch.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Render/MeshBatch.cs @@ -15,7 +15,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render private bool disposed; private float batchDelta; public GLContext GLContext {get; private set; } - private int projALoc, viewALoc, modelALoc, texturedALoc; + private int projALoc, viewALoc, modelALoc, texturedALoc, singleChanneledALoc; private Camera camera; private RenderProgram renderProgram; private ITexture texture; @@ -54,6 +54,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render viewALoc = GLContext.GetUniformLocation(renderProgram.Handle, "view"); projALoc = GLContext.GetUniformLocation(renderProgram.Handle, "projection"); texturedALoc = GLContext.GetUniformLocation(renderProgram.Handle, "textured"); + singleChanneledALoc = GLContext.GetUniformLocation(renderProgram.Handle, "singleChanneled"); vertexBuffers.defineVertexAttributes(definitions: definitions); GLContext.UniformMatrix4fv(projALoc, 1, false, camera.ProjectionMatrix.ToColumnMajorArray()); @@ -80,15 +81,18 @@ namespace SlatedGameToolkit.Framework.Graphics.Render IMoveable moveable = mesh as IMoveable; if (moveable != null) { if (GameEngine.UpdatesPerSecond <= 0) { - camera.Position = camera.MoveTo; + moveable.Position = moveable.MoveTo; } 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) { Flush(); this.texture = mesh.Texture; GLContext.Uniform1i(texturedALoc, texture == null ? 0 : 1); + if (texture != null) { + GLContext.Uniform1i(singleChanneledALoc, texture.SingleChanneled ? 1 : 0); + } } ValueTuple[] vertices = mesh.Vertices; uint[] indices = mesh.Elements; @@ -134,7 +138,9 @@ namespace SlatedGameToolkit.Framework.Graphics.Render } protected virtual void Flush() { - texture?.Use(); + if (texture != null) { + GLContext.BindTexture(TextureTarget.Texture2D, texture.Handle); + } renderProgram.Use(); if (GameEngine.UpdatesPerSecond <= 0) { camera.Position = camera.MoveTo; diff --git a/src/SlatedGameToolkit.Framework/Graphics/Render/RectangleMesh.cs b/src/SlatedGameToolkit.Framework/Graphics/Render/RectangleMesh.cs index 3e9d767..f71fdd0 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Render/RectangleMesh.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Render/RectangleMesh.cs @@ -11,8 +11,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render private bool changed; private Vector3 rotation; private Vector2 origin, dimensions; - public readonly Vector2[] textureCoords = new Vector2[4]; - public ValueTuple[] vertices = new ValueTuple[4]; + private ValueTuple[] vertices = new ValueTuple[4]; private uint[] indices = new uint[] {0, 1, 3, 1, 2, 3}; public ValueTuple[] 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 Vector3 Rotation @@ -99,24 +111,33 @@ namespace SlatedGameToolkit.Framework.Graphics.Render public Color Color { get; set; } - public RectangleMesh(ITexture texture, Color color) + public RectangleMesh(RectangleF textureRegion, ITexture texture, Color color) { this.Texture = texture; this.Color = color; - this.textureCoords[0].X = 0; - this.textureCoords[0].Y = 1; + this.vertices[0].Item2.X = textureRegion.X; + this.vertices[0].Item2.Y = textureRegion.Y; - this.textureCoords[1].X = 1; - this.textureCoords[1].Y = 1; + this.vertices[1].Item2.X = textureRegion.X + textureRegion.Width; + this.vertices[1].Item2.Y = textureRegion.Y; - this.textureCoords[2].X = 1; - this.textureCoords[2].Y = 0; + this.vertices[2].Item2.X = textureRegion.X + textureRegion.Width; + this.vertices[2].Item2.Y = textureRegion.Y + textureRegion.Height; - this.textureCoords[3].X = 0; - this.textureCoords[3].Y = 0; + this.vertices[3].Item2.X = textureRegion.X; + 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() { if (!changed) return; @@ -128,7 +149,7 @@ namespace SlatedGameToolkit.Framework.Graphics.Render 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; } diff --git a/src/SlatedGameToolkit.Framework/Graphics/Text/BitmapFont.cs b/src/SlatedGameToolkit.Framework/Graphics/Text/BitmapFont.cs new file mode 100644 index 0000000..0d474a7 --- /dev/null +++ b/src/SlatedGameToolkit.Framework/Graphics/Text/BitmapFont.cs @@ -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 glyphIndexCache; + private FontBitmap[] textureBuffers; + private int drawingTo = 0; + + GLContext glContext; + + /// + /// Creates a font with the given data. + /// + /// The byte array containing all ttf data. + /// The OpenGL context to use to associate with. + /// The size of the caches. + /// The length of one side of a bitmap, therefore, the total would be this squared. + /// The number of backing textures to use. + /// An initial set of characters to load. + 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(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) + { + } + + /// + /// Loads the requested character. + /// + /// The character to load. + /// True if already loaded or successfully loaded, false if could not load. + 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); + } + } + } +} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs deleted file mode 100644 index 32cd4dd..0000000 --- a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicFont.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace SlatedGameToolkit.Framework.Graphics.Text -{ - public class DynamicFont { - private readonly Dictionary glyphLocations = new Dictionary(); - - public DynamicFont() { - - } - } -} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs b/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs deleted file mode 100644 index a203699..0000000 --- a/src/SlatedGameToolkit.Framework/Graphics/Text/DynamicGlyph.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Textures/ITexture.cs b/src/SlatedGameToolkit.Framework/Graphics/Textures/ITexture.cs index 4f3cd85..9d3b57d 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Textures/ITexture.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Textures/ITexture.cs @@ -1,8 +1,16 @@ +using System; + namespace SlatedGameToolkit.Framework.Graphics.Textures { - public interface ITexture + public interface ITexture : IDisposable { + /// + /// Whether or not this model uses a texture that is single chanelled. + /// + /// true for single channeled. + bool SingleChanneled { get; } uint Handle {get;} - void Use(); + uint Width {get;} + uint Height {get;} } } \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Graphics/Textures/Texture.cs b/src/SlatedGameToolkit.Framework/Graphics/Textures/Texture.cs index 4557aa1..b1fc453 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Textures/Texture.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Textures/Texture.cs @@ -7,13 +7,19 @@ using SlatedGameToolkit.Framework.AssetSystem; namespace SlatedGameToolkit.Framework.Graphics.Textures { public class Texture : IAssetUseable, ITexture { - public readonly int width, height; + public readonly uint width, height; private bool disposed; private GLContext glContext; private uint handle; public uint Handle {get { return handle; }} + public uint Width => width; + + public uint Height => height; + + public bool SingleChanneled => false; + /// /// Creates an OpenGL Texture2D in the given GL Context. /// @@ -21,8 +27,8 @@ namespace SlatedGameToolkit.Framework.Graphics.Textures /// The openGL context to associate this with. If null, will use the currently active context. Defaults to null. public unsafe Texture(TextureData textureData, GLContext context = null) { this.glContext = context ?? WindowContextsManager.CurrentGL; - this.width = textureData.width; - this.height = textureData.height; + this.width = (uint)textureData.width; + this.height = (uint)textureData.height; uint[] handles = new uint[1]; glContext.GenTextures(1, handles); 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.TextureMagFilter, (int)TextureMagFilter.Linear); 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.GenerateMipmap(OpenGL.TextureTarget.Texture2D); diff --git a/src/SlatedGameToolkit.Framework/Graphics/Window/WindowContextsManager.cs b/src/SlatedGameToolkit.Framework/Graphics/Window/WindowContextsManager.cs index 0a3095b..aa24cea 100644 --- a/src/SlatedGameToolkit.Framework/Graphics/Window/WindowContextsManager.cs +++ b/src/SlatedGameToolkit.Framework/Graphics/Window/WindowContextsManager.cs @@ -16,7 +16,12 @@ namespace SlatedGameToolkit.Framework.Graphics.Window get { return current; } + + set { + value.MakeCurrent(); + } } + private static Dictionary existingWindows = new Dictionary(); /// diff --git a/src/SlatedGameToolkit.Framework/Resources/default.frag b/src/SlatedGameToolkit.Framework/Resources/default.frag index da9e2f1..15c20d9 100644 --- a/src/SlatedGameToolkit.Framework/Resources/default.frag +++ b/src/SlatedGameToolkit.Framework/Resources/default.frag @@ -4,13 +4,19 @@ out vec4 outputColor; in vec2 texCoord; in vec4 color; +uniform bool singleChanneled; uniform bool textured; uniform sampler2D texture0; void main() { 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 { outputColor = color; } diff --git a/src/SlatedGameToolkit.Framework/Resources/default.vert b/src/SlatedGameToolkit.Framework/Resources/default.vert index a02eaf5..78902c0 100644 --- a/src/SlatedGameToolkit.Framework/Resources/default.vert +++ b/src/SlatedGameToolkit.Framework/Resources/default.vert @@ -14,6 +14,5 @@ void main() { texCoord = aTexCoord; color = aColor; - gl_Position = projection * view * models * vec4(aPosition, 1.0); } \ No newline at end of file diff --git a/src/SlatedGameToolkit.Framework/Utilities/RectangleUtils.cs b/src/SlatedGameToolkit.Framework/Utilities/RectangleUtils.cs new file mode 100644 index 0000000..7ecc883 --- /dev/null +++ b/src/SlatedGameToolkit.Framework/Utilities/RectangleUtils.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/SlatedGameToolkit.Tools/Resources/Playground/earwig_factory_rg.ttf b/src/SlatedGameToolkit.Tools/Resources/Playground/earwig_factory_rg.ttf new file mode 100644 index 0000000..769d55a Binary files /dev/null and b/src/SlatedGameToolkit.Tools/Resources/Playground/earwig_factory_rg.ttf differ diff --git a/src/SlatedGameToolkit.Tools/SlatedGameToolkit.Tools.csproj b/src/SlatedGameToolkit.Tools/SlatedGameToolkit.Tools.csproj index 2b669f4..736253b 100644 --- a/src/SlatedGameToolkit.Tools/SlatedGameToolkit.Tools.csproj +++ b/src/SlatedGameToolkit.Tools/SlatedGameToolkit.Tools.csproj @@ -10,6 +10,7 @@ netcoreapp3.1 1.0.0.0 A tool to help with developing a game using SlatedGameToolkit. + true diff --git a/src/SlatedGameToolkit.Tools/Utilities/Playground/MainState.cs b/src/SlatedGameToolkit.Tools/Utilities/Playground/MainState.cs index f7b61c7..ae7e089 100644 --- a/src/SlatedGameToolkit.Tools/Utilities/Playground/MainState.cs +++ b/src/SlatedGameToolkit.Tools/Utilities/Playground/MainState.cs @@ -1,14 +1,18 @@ using System; using System.Drawing; +using System.IO; using System.Numerics; +using System.Runtime.InteropServices; using SDL2; using SlatedGameToolkit.Commons.Loaders; using SlatedGameToolkit.Framework.Graphics.Render; +using SlatedGameToolkit.Framework.Graphics.Text; using SlatedGameToolkit.Framework.Graphics.Textures; using SlatedGameToolkit.Framework.Graphics.Window; using SlatedGameToolkit.Framework.Input.Devices; using SlatedGameToolkit.Framework.StateSystem; using SlatedGameToolkit.Framework.StateSystem.States; +using StbTrueTypeSharp; namespace SlatedGameToolkit.Tools.Utilities.Playground { @@ -17,6 +21,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground private WindowContext window; private Camera2D camera; private MeshBatch renderer; + private BitmapFont font; private Texture logoTexture, fillerTexture; private RectangleMesh logo, textureTester, untextured; @@ -36,6 +41,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground { logoTexture.Dispose(); fillerTexture.Dispose(); + font.Dispose(); renderer.Dispose(); window.Dispose(); } @@ -45,13 +51,13 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground return "main state"; } - public void Initialize(StateManager manager) + public unsafe void Initialize(StateManager manager) { window = new WindowContext("SlatedGameToolkit Playground"); camera = new Camera2D(2, 2); 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.Width = 0.5f; logo.Height = 0.5f; @@ -59,7 +65,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground 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.Width = 0.15f; textureTester.Height = 0.15f; @@ -70,6 +76,8 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground untextured.Height = 0.1f; untextured.X = 0.25f; untextured.Y = - 0.15f; + + font = new BitmapFont("Resources/Playground/earwig_factory_rg.ttf"); } public void Render(double delta) @@ -78,6 +86,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground renderer.Draw(logo); renderer.Draw(textureTester); renderer.Draw(untextured); + font.Draw(renderer, delta, 0.25f, -0.25f, "123", Color.White); renderer.End(); } diff --git a/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs b/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs index d76de4d..276b289 100644 --- a/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs +++ b/tests/SlatedGameToolkit.Framework.Tests/Utilities/Collections/Caching/LRUCacheTests.cs @@ -50,5 +50,25 @@ namespace SlatedGameToolkit.Framework.Tests.Utilities.Collections.Caching cache[5] = 5; Assert.IsTrue(cache.ContainsKey(2)); } + + [Test] + public void ComputeIfNonexistentTest() { + LRUCache cache = new LRUCache(4); + for (int i = 0; i < cache.MaxLength; i++) { + cache.ComputeIfNonExistent(i, (a) => a); + } + } + + [Test] + public void ComputeIfNonexistentTestExists() { + LRUCache cache = new LRUCache(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)); + } + } } } \ No newline at end of file