257 lines
8.9 KiB
C#
257 lines
8.9 KiB
C#
|
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;
|
|||
|
public float Right;
|
|||
|
public float Top;
|
|||
|
public float Bottom;
|
|||
|
|
|||
|
public int Width;
|
|||
|
public float Height;
|
|||
|
|
|||
|
public float AspectRatio;
|
|||
|
|
|||
|
public float BearingX;
|
|||
|
public float BearingY;
|
|||
|
public float Advance;
|
|||
|
}
|
|||
|
|
|||
|
private const int SheetWidth = 1024;
|
|||
|
private const int SheetHeight = 512;
|
|||
|
private int ScreenWidth, ScreenHeight;
|
|||
|
private int CharacterTextureSheet;
|
|||
|
private CharacterInfo[] characters;
|
|||
|
|
|||
|
public Color fontColor { get; set; } = Color.Black;
|
|||
|
|
|||
|
private string GetFontPath()
|
|||
|
{
|
|||
|
string fontFolder = System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
|||
|
|
|||
|
// Only uses Arial, add more fonts here if wanted
|
|||
|
string path = Path.Combine(fontFolder, "arial.ttf");
|
|||
|
if (File.Exists(path))
|
|||
|
{
|
|||
|
return path;
|
|||
|
}
|
|||
|
|
|||
|
throw new Exception($"Profiler exception. Required font Courier New or Arial not installed to {fontFolder}");
|
|||
|
}
|
|||
|
|
|||
|
public void InitalizeTextures()
|
|||
|
{
|
|||
|
// Create and init some vars
|
|||
|
uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight];
|
|||
|
int x;
|
|||
|
int y;
|
|||
|
int lineOffset;
|
|||
|
int maxHeight;
|
|||
|
|
|||
|
x = y = lineOffset = maxHeight = 0;
|
|||
|
characters = new CharacterInfo[94];
|
|||
|
|
|||
|
// Get font
|
|||
|
var font = new FontFace(File.OpenRead(GetFontPath()));
|
|||
|
|
|||
|
// Update raw data for each character
|
|||
|
for (int i = 0; i < 94; i++)
|
|||
|
{
|
|||
|
var surface = RenderSurface((char)(i + 33), font, out var xBearing, out var yBearing, out var advance);
|
|||
|
|
|||
|
characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset);
|
|||
|
characters[i].BearingX = xBearing;
|
|||
|
characters[i].BearingY = yBearing;
|
|||
|
characters[i].Advance = advance;
|
|||
|
|
|||
|
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].BearingX /= characters[i].Width;
|
|||
|
characters[i].BearingY /= maxHeight;
|
|||
|
characters[i].Advance /= characters[i].Width;
|
|||
|
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 UpdateScreenHeight(int height)
|
|||
|
{
|
|||
|
ScreenHeight = height;
|
|||
|
}
|
|||
|
|
|||
|
public float DrawText(string text, float x, float y, float height, bool draw = true)
|
|||
|
{
|
|||
|
float originalX = x;
|
|||
|
|
|||
|
// Skip out of bounds draw
|
|||
|
if (y < height * -2 || y > ScreenHeight + height * 2)
|
|||
|
{
|
|||
|
draw = false;
|
|||
|
}
|
|||
|
|
|||
|
if (draw)
|
|||
|
{
|
|||
|
// 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 width = (charInfo.AspectRatio * height);
|
|||
|
x += (charInfo.BearingX * charInfo.AspectRatio) * width;
|
|||
|
float right = x + width;
|
|||
|
if (draw)
|
|||
|
{
|
|||
|
DrawChar(charInfo, x, right, y + height * (charInfo.Height - charInfo.BearingY), y - height * charInfo.BearingY);
|
|||
|
}
|
|||
|
x = right + charInfo.Advance * charInfo.AspectRatio + 1;
|
|||
|
}
|
|||
|
|
|||
|
if (draw)
|
|||
|
{
|
|||
|
GL.End();
|
|||
|
|
|||
|
// Cleanup for caller
|
|||
|
GL.BindTexture(TextureTarget.Texture2D, 0);
|
|||
|
GL.Disable(EnableCap.Texture2D);
|
|||
|
GL.Disable(EnableCap.Blend);
|
|||
|
}
|
|||
|
|
|||
|
// Return width of rendered text
|
|||
|
return x - originalX;
|
|||
|
}
|
|||
|
|
|||
|
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, out float xBearing, out float yBearing, out float advance)
|
|||
|
{
|
|||
|
var glyph = font.GetGlyph(c, 64);
|
|||
|
xBearing = glyph.HorizontalMetrics.Bearing.X;
|
|||
|
yBearing = glyph.RenderHeight - glyph.HorizontalMetrics.Bearing.Y;
|
|||
|
advance = glyph.HorizontalMetrics.Advance;
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|