From e550889328c368151dcc2658b80bdc144c956d0d Mon Sep 17 00:00:00 2001 From: Andy Adshead Date: Sat, 26 Jan 2019 01:17:57 +0000 Subject: [PATCH] Simple font rendering --- Ryujinx.Profiler/Ryujinx.Profiler.csproj | 2 + Ryujinx.Profiler/UI/ProfileWindow.cs | 19 +- .../UI/SharpFontHelpers/FontService.cs | 192 ++++++++++++++++++ 3 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs diff --git a/Ryujinx.Profiler/Ryujinx.Profiler.csproj b/Ryujinx.Profiler/Ryujinx.Profiler.csproj index 3fc6117096..4d33758c2b 100644 --- a/Ryujinx.Profiler/Ryujinx.Profiler.csproj +++ b/Ryujinx.Profiler/Ryujinx.Profiler.csproj @@ -3,10 +3,12 @@ netcoreapp2.1 win10-x64;osx-x64;linux-x64 + true + diff --git a/Ryujinx.Profiler/UI/ProfileWindow.cs b/Ryujinx.Profiler/UI/ProfileWindow.cs index 1912013c86..e680eaa9e1 100644 --- a/Ryujinx.Profiler/UI/ProfileWindow.cs +++ b/Ryujinx.Profiler/UI/ProfileWindow.cs @@ -2,6 +2,7 @@ using OpenTK.Graphics.OpenGL; using System; using System.ComponentModel; +using Ryujinx.Profiler.UI.SharpFontHelpers; namespace Ryujinx { @@ -9,6 +10,7 @@ namespace Ryujinx { private bool visible = true; public bool visibleChanged; + private FontService fontService; public ProfileWindow() : base(400, 720) @@ -34,6 +36,8 @@ namespace Ryujinx protected override void OnLoad(EventArgs e) { GL.ClearColor(Color.MidnightBlue); + fontService = new FontService(); + fontService.InitalizeTextures(); } #endregion @@ -97,18 +101,11 @@ namespace Ryujinx return; } - GL.Clear(ClearBufferMask.ColorBufferBit); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - GL.Begin(BeginMode.Triangles); - - GL.Color3(Color.MidnightBlue); - GL.Vertex2(0.0f, 0.0f); - GL.Color3(Color.SpringGreen); - GL.Vertex2(50.0f, 100.0f); - GL.Color3(Color.Ivory); - GL.Vertex2(100.0f, 0.0f); - - GL.End(); + GL.ClearColor(Color.White); + fontService.fontColor = Color.Black; + fontService.DrawText("This is a test", 50, 50, 32); this.SwapBuffers(); } diff --git a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs new file mode 100644 index 0000000000..6f1bc1f4c8 --- /dev/null +++ b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs @@ -0,0 +1,192 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using SharpFont; + +namespace Ryujinx.Profiler.UI.SharpFontHelpers +{ + public class FontService + { + private struct CharacterInfo + { + public float left, right, bottom, top; + public float height, aspectRatio; + public int width; + } + + private const int SheetWidth = 256; + private const int SheetHeight = 256; + private int characterTextureSheet; + private CharacterInfo[] characters; + + public float characterSpacing = 1; + public Color fontColor = Color.Black; + + public void InitalizeTextures() + { + // Create and init some vars + uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight]; + int x, y, lineOffset, maxHeight; + x = y = lineOffset = maxHeight = 0; + characters = new CharacterInfo[94]; + + // Get font + var font = new FontFace(File.OpenRead(Path.Combine(Environment.GetFolderPath( + Environment.SpecialFolder.ApplicationData), @"RyuFs\system\fonts\FontStandard.ttf"))); + + // Update raw data for each character + for (int i = 0; i < 94; i++) + { + var surface = RenderSurface((char)(i + 33), font); + + characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset); + + if (maxHeight < characters[i].height) + maxHeight = (int)characters[i].height; + } + + // Fix height for characters shorter than line height + for (int i = 0; i < 94; i++) + { + characters[i].height /= maxHeight; + characters[i].aspectRatio = (float)characters[i].width / maxHeight; + } + + // Convert raw data into texture + characterTextureSheet = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, characterTextureSheet); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, SheetWidth, SheetHeight, 0, PixelFormat.Rgba, PixelType.UnsignedInt8888, rawCharacterSheet); + + GL.BindTexture(TextureTarget.Texture2D, 0); + } + + public void DrawText(string text, float x, float y, float height) + { + // Use font map texture + GL.BindTexture(TextureTarget.Texture2D, characterTextureSheet); + + // Enable blending and textures + GL.Enable(EnableCap.Texture2D); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + + // Draw all characters + GL.Begin(PrimitiveType.Triangles); + GL.Color4(fontColor); + + for (int i = 0; i < text.Length; i++) + { + if (text[i] == ' ') + { + x += height / 4; + continue; + } + + CharacterInfo charInfo = characters[text[i] - 33]; + float right = x + (charInfo.aspectRatio * height); + DrawChar(charInfo, x, right, y + height * charInfo.height, y); + x = right + characterSpacing; + } + + GL.End(); + + // Cleanup for caller + GL.BindTexture(TextureTarget.Texture2D, 0); + GL.Disable(EnableCap.Texture2D); + GL.Disable(EnableCap.Blend); + } + + private void DrawChar(CharacterInfo charInfo, float left, float right, float top, float bottom) + { + GL.TexCoord2(charInfo.left, charInfo.bottom); GL.Vertex2(left, bottom); + GL.TexCoord2(charInfo.left, charInfo.top); GL.Vertex2(left, top); + GL.TexCoord2(charInfo.right, charInfo.top); GL.Vertex2(right, top); + + GL.TexCoord2(charInfo.right, charInfo.top); GL.Vertex2(right, top); + GL.TexCoord2(charInfo.right, charInfo.bottom); GL.Vertex2(right, bottom); + GL.TexCoord2(charInfo.left, charInfo.bottom); GL.Vertex2(left, bottom); + } + + public unsafe Surface RenderSurface(char c, FontFace font) + { + var glyph = font.GetGlyph(c, 32); + var surface = new Surface + { + Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight), + Width = glyph.RenderWidth, + Height = glyph.RenderHeight, + Pitch = glyph.RenderWidth + }; + + var stuff = (byte*)surface.Bits; + for (int i = 0; i < surface.Width * surface.Height; i++) + *stuff++ = 0; + + glyph.RenderTo(surface); + + return surface; + } + + private CharacterInfo UpdateTexture(Surface surface, ref uint[] rawCharMap, ref int posX, ref int posY, ref int lineOffset) + { + int width = surface.Width; + int height = surface.Height; + int len = width * height; + byte[] data = new byte[len]; + + // Get character bitmap + Marshal.Copy(surface.Bits, data, 0, len); + + // Find a slot + if (posX + width > SheetWidth) + { + posX = 0; + posY += lineOffset; + lineOffset = 0; + } + + // Update lineoffset + if (lineOffset < height) + { + lineOffset = height + 1; + } + + // Copy char to sheet + for (int y = 0; y < height; y++) + { + int destOffset = (y + posY) * SheetWidth + posX; + int sourceOffset = y * width; + for (int x = 0; x < width; x++) + { + rawCharMap[destOffset + x] = (uint)((0xFFFFFF << 8) | data[sourceOffset + x]); + } + } + + // Generate character info + CharacterInfo charInfo = new CharacterInfo() + { + left = (float)posX / SheetWidth, + right = (float)(posX + width) / SheetWidth, + top = (float)(posY - 1) / SheetHeight, + bottom = (float)(posY + height) / SheetHeight, + width = width, + height = height, + }; + + // Update x + posX += width + 1; + + // Give the memory back + Marshal.FreeHGlobal(surface.Bits); + return charInfo; + } + } +} \ No newline at end of file