Files
glitchy-engine-beef/GlitchyEngine/src/Renderer/Text/FontRenderer.bf
T

283 lines
9.3 KiB
Beef

using System;
using FreeType;
using System.Diagnostics;
using GlitchyEngine.Math;
using System.Collections;
using static FreeType.HarfBuzz;
using internal GlitchyEngine.Renderer.Text;
using internal GlitchyEngine.Renderer;
namespace GlitchyEngine.Renderer.Text
{
public static class FontRenderer
{
internal static FT_Library s_Library;
public static Effect _msdfEffect;
internal static bool s_isInitialized;
internal static void Init()
{
if(s_isInitialized)
return;
InitFreetype();
_msdfEffect = new Effect("content\\Shaders\\msdfShader.hlsl");
s_isInitialized = true;
}
internal static void Deinit()
{
_msdfEffect.ReleaseRef();
DeinitFreetype();
s_isInitialized = false;
}
private static void InitFreetype()
{
if(s_Library == null)
{
var res = FreeType.Init_FreeType(&s_Library);
Log.EngineLogger.Assert(res.Success, scope $"Failed to initialize freetype ({(int)res}): {res}");
}
}
private static void DeinitFreetype()
{
FreeType.Done_FreeType(s_Library);
}
/** @brief Draws a given text using a specified font stack and renderer.
* @param renderer The 2D renderer that will be used to draw the text.
* @param font the Fontstack that will be used to draw the text.
* @param x The horizontal position of the text (i.e. distance between left side of viewport and the left side of the left-most character, well not really but close enough).
* @param y The vertical position of the text (i.e. distance between top side of viewport and the top side of the top-most character, well not really but close enough).
* @param fontSize The font size in pixels. Note: due to technical reasons the actual size might deviate from the specified size.
* @param fontColor The (default) color used for glyphs that don't have color (e.g. letters or emojies if the font doesn't use bitmaps for them).
* @param bitmapColor The (default) color used for glyphs that are bitmaps (e.g. emojies, if the font provies them as bitmap).
* @param lineGapOffset Can be used to manually increase or decrease the gap between lines.
*/
public static void DrawText(Font font, String text, float x, float y, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineGapOffset = 0)
{
if(text.IsWhiteSpace)
return;
Renderer2D.Flush();
// TODO: this is very not good!
var lastEffect = Renderer2D.[Friend]s_currentEffect;
Renderer2D.[Friend]s_currentEffect = _msdfEffect..AddRef();
// TODO: oh no....
// Copy viewProjection from current effect
Matrix viewProjection = lastEffect.Variables["ViewProjection"].[Friend]GetData<Matrix>();
_msdfEffect.Variables["ViewProjection"].SetData(viewProjection);
float scale = (float)fontSize / (float)font._fontSize;
_msdfEffect.Variables["screenPixelRange"].SetData(scale * 4.0f);
Vector2 unitRange = Vector2((float)font._range) / Vector2(font._atlas.Width, font._atlas.Height);
_msdfEffect.Variables["UnitRange"].SetData(unitRange);
// Space between two baselines
float linespace = (((font._face.size.metrics.ascender - font._face.size.metrics.descender) / 64) + lineGapOffset) * scale;
// The line we are writing on
float baseline = y;// + linespace;
//renderer.Draw(null, x, baseline, 10000, 1, .Blue);
// Position of the next character on the line
float penPosition = x;
// how many lines we moved up or down (e.g. after a \n)
int movedLines = 0;
List<Texture2D> atlasses = scope .();
// We render every char with a slightly greater depth, so that the chars don't cull each other.
// In order to do that we increment an integer for each glyph and use it as the depth for the next one.
// Whenn passing the depth to the renderer we treat the integer as the binary representation of a float.
// Therefore every increment will increment the mantissa of the float and is therefore equivalent to the
// smallest possible increase a float can represent.
// Note: This might do funky stuff when objects are very close to each other. But realistically whe have to do
// about 8 million (2^23) increments to reach a depth of 1 so I think it's safe enough.
//float f = 1.0f;
//int32 depthInt = *(int32*)&f;
// Shape!!!!!!!!!!!!!!!!!!!
// 1. Create a buffer and put your text in it:
hb_buffer_t *buf;
buf = hb_buffer_create();
hb_buffer_add_utf8(buf, text.CStr(), -1, 0, -1);
// 2. Set the script, language and direction of the buffer.
hb_buffer_set_direction(buf, .HB_DIRECTION_LTR);
hb_buffer_set_script(buf, .HB_SCRIPT_LATIN);
hb_buffer_set_language(buf, hb_language_from_string("en".CStr(), -1));
// 3. Get the face
hb_font_t* hb_font = font.[Friend]font;
// 4. Shape:
hb_shape(hb_font, buf, null, 0);
// 5. Get the glyph and position information.
uint32 glyph_count = ?;
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
for (uint32 i < glyph_count)
{
hb_codepoint_t glyphid = glyph_info[i].codepoint;
hb_position_t x_offset = glyph_pos[i].x_offset;
hb_position_t y_offset = glyph_pos[i].y_offset;
hb_position_t x_advance = glyph_pos[i].x_advance;
hb_position_t y_advance = glyph_pos[i].y_advance;
// Get glyph from Font
var glyphDesc = font.GetGlyph(glyphid);
// This never happens
Debug.Assert(glyphDesc != null);
// The glyph might belong to a fallback font
var glyphFont = glyphDesc.Font;
float glyphFontScale = (float)fontSize / glyphFont._fontSize;
Texture2D atlas = glyphFont._atlas;
// Add reference to atlas in case it is recreated during rendering
if(!atlasses.Contains(atlas))
{
atlasses.Add(atlas..AddRef());
}
Vector2 atlasSize = .(atlas.Width, atlas.Height);
float adjustToPenX = glyphDesc.AdjustToPen;
adjustToPenX *= glyphFontScale;
float adjustToBaseline = glyphDesc.AdjustToBaseLine;
adjustToBaseline *= glyphFontScale;
// Rectangle on the screen
Vector4 viewportRect = .(
penPosition + adjustToPenX,
baseline + adjustToBaseline,
glyphDesc.Width * glyphFontScale,
glyphDesc.Height * glyphFontScale);
// Rectangle on the font atlas
Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.Width, glyphDesc.Height);
Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor;
texRect /= Vector4(atlasSize, atlasSize);
Renderer2D.DrawQuad(Vector3(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2, 1), .(viewportRect.Z, viewportRect.W), 0, .Red);
Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, glyphColor, texRect);
Renderer2D.DrawQuad(Vector3(penPosition, baseline, -1), .(1), 0, .Green);
penPosition += (x_advance / 64) * glyphFontScale;
baseline += (y_advance / 64) * glyphFontScale;
}
/*
// enumerate through the unicode codepoints
for(char32 char in text.DecodedChars)
{
if(char == '\n')
{
movedLines++;
continue;
}
// move baseline if necessary
if(movedLines != 0)
{
// move baseline
baseline -= linespace * movedLines;
// TODO: make carriage return optional?
// return pen to start of line
penPosition = x;
movedLines = 0;
}
//renderer.Draw(null, penPosition, baseline - fontSize, 1, fontSize, .Lime);
// Get glyph from Font
var glyphDesc = font.GetGlyph(char);
// This never happens
Debug.Assert(glyphDesc != null);
// The glyph might belong to a fallback font
var glyphFont = glyphDesc.Font;
float glyphFontScale = (float)fontSize / glyphFont._fontSize;
Texture2D atlas = glyphFont._atlas;
// Add reference to atlas in case it is recreated during rendering
if(!atlasses.Contains(atlas))
{
atlasses.Add(atlas..AddRef());
}
Vector2 atlasSize = .(atlas.Width, atlas.Height);
float adjustToPenX = glyphDesc.AdjustToPen;
adjustToPenX *= glyphFontScale;
float adjustToBaseline = glyphDesc.AdjustToBaseLine;
adjustToBaseline *= glyphFontScale;
// Rectangle on the screen
Vector4 viewportRect = .(
penPosition + adjustToPenX,
baseline + adjustToBaseline,
glyphDesc.Width * glyphFontScale,
glyphDesc.Height * glyphFontScale);
// Rectangle on the font atlas
Vector4 texRect = .(glyphDesc.MapCoord.X, glyphDesc.MapCoord.Y, glyphDesc.Width, glyphDesc.Height);
Color glyphColor = glyphDesc.IsBitmap ? bitmapColor : fontColor;
texRect /= Vector4(atlasSize, atlasSize);
Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, glyphColor, texRect);
//renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, *(float*)(&depthInt), texRect);
penPosition += glyphDesc.Advance * glyphFontScale;
// Increase depth for the next glyph
//depthInt--;
}
*/
Renderer2D.Flush();
// TODO: not good!
// Change back effect
_msdfEffect.ReleaseRef();
Renderer2D.[Friend]s_currentEffect = lastEffect;
// release all atlas textures
for(int i < atlasses.Count)
{
atlasses[i].ReleaseRef();
}
}
}
}