mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Basic Harfbuzz support and use MSDGen with Skia
This commit is contained in:
@@ -4,6 +4,7 @@ using GlitchyEngine.Math;
|
||||
using GlitchyEngine.Core;
|
||||
using System.Collections;
|
||||
using msdfgen;
|
||||
using static FreeType.HarfBuzz;
|
||||
|
||||
using internal GlitchyEngine.Renderer.Text;
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
public int32 Width, Height;
|
||||
|
||||
public double TranslationX, TranslationY;
|
||||
//public double Scale;
|
||||
|
||||
/// How many pixels we have to move the pen after drawing this glyph.
|
||||
public float Advance;
|
||||
@@ -50,7 +52,8 @@ namespace GlitchyEngine.Renderer.Text
|
||||
internal Texture2D _atlas ~ _?.ReleaseRef();
|
||||
private Int3 _atlasSize;
|
||||
|
||||
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ DeleteDictionaryAndValues!(_);
|
||||
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ delete _;//DeleteDictionaryAndValues!(_);
|
||||
private Dictionary<uint32, GlyphDescriptor> _glyphsById = new .() ~ DeleteDictionaryAndValues!(_);
|
||||
GlyphDescriptor _missingGlyph;
|
||||
|
||||
private SamplerState _sampler ~ _.ReleaseRef();
|
||||
@@ -60,6 +63,10 @@ namespace GlitchyEngine.Renderer.Text
|
||||
|
||||
internal double _range = 4.0;
|
||||
|
||||
private hb_blob_t* blob ~ hb_blob_destroy(_);
|
||||
private hb_face_t* face ~ hb_face_destroy(_);
|
||||
private hb_font_t* font ~ hb_font_destroy(_);
|
||||
|
||||
/**
|
||||
* Gets or sets the fallback Font for this Font.
|
||||
* The Fallback font will be used to render Glyphs that are not defined in the current font.
|
||||
@@ -113,8 +120,6 @@ namespace GlitchyEngine.Renderer.Text
|
||||
// Set default sampler
|
||||
Sampler = null;
|
||||
|
||||
_glyphs.Add('\0', new GlyphDescriptor(){Font = this});
|
||||
|
||||
_fontSize = fontSize;
|
||||
_faceIndex = faceIndex;
|
||||
_hasColor = hasColor;
|
||||
@@ -130,7 +135,23 @@ namespace GlitchyEngine.Renderer.Text
|
||||
|
||||
_range = 4.0 / _geometryScaler;
|
||||
|
||||
GlyphDescriptor nullDesc = new GlyphDescriptor(){Font = this};
|
||||
nullDesc.GlyphIndex = FreeType.Get_Char_Index(_face, '\0');
|
||||
_glyphs.Add('\0', nullDesc);
|
||||
_glyphsById.Add(nullDesc.GlyphIndex, nullDesc);
|
||||
|
||||
LoadGlyphs(firstChar, charCount);
|
||||
|
||||
// HarfBuzz
|
||||
{
|
||||
blob = hb_blob_create_from_file(fontPath.CStr()); /* or hb_blob_create_from_file_or_fail() */
|
||||
face = hb_face_create(blob, 0);
|
||||
font = hb_font_create(face);
|
||||
|
||||
hb_font_set_scale(font, (.)fontSize * 64, (.)fontSize * 64);
|
||||
}
|
||||
|
||||
//TestMSDF();
|
||||
}
|
||||
|
||||
public void LoadGlyphs(char32 firstChar, uint32 charCount)
|
||||
@@ -140,6 +161,37 @@ namespace GlitchyEngine.Renderer.Text
|
||||
UpdateAtlas();
|
||||
}
|
||||
|
||||
public void LoadGlyphs(uint32 firstGlyph, uint32 charCount)
|
||||
{
|
||||
ExtendRange(firstGlyph, firstGlyph + charCount);
|
||||
|
||||
UpdateAtlas();
|
||||
}
|
||||
|
||||
internal GlyphDescriptor GetGlyph(uint32 glyphId, bool allowDynamicLoading = true)
|
||||
{
|
||||
GlyphDescriptor desc;
|
||||
|
||||
if(_glyphsById.TryGetValue(glyphId, out desc))
|
||||
{
|
||||
// if the char is not defined in the current font try to load it from the fallback
|
||||
if(desc == null && _fallback != null)
|
||||
{
|
||||
desc = _fallback.GetGlyph(glyphId, allowDynamicLoading);
|
||||
}
|
||||
}
|
||||
else if(allowDynamicLoading)
|
||||
{
|
||||
// TODO: make reloading optional
|
||||
uint32 start = (glyphId >> 4) << 4;
|
||||
LoadGlyphs(start, 64);
|
||||
|
||||
desc = GetGlyph(glyphId, false);
|
||||
}
|
||||
|
||||
return desc ?? _missingGlyph;
|
||||
}
|
||||
|
||||
internal GlyphDescriptor GetGlyph(char32 char, bool allowDynamicLoading = true)
|
||||
{
|
||||
GlyphDescriptor desc;
|
||||
@@ -177,6 +229,8 @@ namespace GlitchyEngine.Renderer.Text
|
||||
desc.Font = this;
|
||||
desc.GlyphIndex = FreeType.Get_Char_Index(_face, char);
|
||||
|
||||
_glyphsById.TryAdd(desc.GlyphIndex, desc);
|
||||
|
||||
// if the char is not contained by the font we set the desc to null
|
||||
if(desc.GlyphIndex == 0)
|
||||
{
|
||||
@@ -191,6 +245,33 @@ namespace GlitchyEngine.Renderer.Text
|
||||
}
|
||||
}
|
||||
|
||||
void ExtendRange(uint32 firstGlyph, uint32 rangeEnd)
|
||||
{
|
||||
// TODO: refactor
|
||||
|
||||
for(uint32 glyphId = firstGlyph; glyphId < rangeEnd; glyphId++)
|
||||
{
|
||||
GlyphDescriptor desc = new GlyphDescriptor();
|
||||
if(_glyphsById.TryAdd(glyphId, desc))
|
||||
{
|
||||
//desc.Face = _face;
|
||||
desc.Font = this;
|
||||
desc.GlyphIndex = glyphId;//FreeType.Get_Char_Index(_face, char);
|
||||
|
||||
// if the char is not contained by the font we set the desc to null
|
||||
if(desc.GlyphIndex == 0)
|
||||
{
|
||||
delete desc;
|
||||
_glyphsById[glyphId] = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
delete desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAtlas()
|
||||
{
|
||||
DrawAtlas();
|
||||
@@ -227,7 +308,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
int32 atlasHeight = _atlasSize.Y;
|
||||
int32 atlasArray = _atlasSize.Z;
|
||||
|
||||
for(var (char, desc) in _glyphs)
|
||||
for(var (char, desc) in _glyphsById)
|
||||
{
|
||||
if(desc == null || desc.IsCalculated)
|
||||
{
|
||||
@@ -324,7 +405,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
}
|
||||
}
|
||||
|
||||
for(let (char, desc) in _glyphs)
|
||||
for(let (char, desc) in _glyphsById)
|
||||
{
|
||||
if(desc == null)
|
||||
continue;
|
||||
@@ -356,6 +437,9 @@ namespace GlitchyEngine.Renderer.Text
|
||||
|
||||
desc.Advance = (float)(advance * _geometryScaler);
|
||||
|
||||
//shape.OrientContours();
|
||||
msdfgen.ResolveShapeGeometry(shape);
|
||||
|
||||
shape.Normalize();
|
||||
|
||||
var bounds = shape.GetBounds();
|
||||
@@ -407,6 +491,8 @@ namespace GlitchyEngine.Renderer.Text
|
||||
desc.TranslationX = translationX;
|
||||
desc.TranslationY = translationY;
|
||||
|
||||
//desc.Scale = scale;
|
||||
|
||||
desc.AdjustToBaseLine = (float)(-translationY * _geometryScaler);
|
||||
|
||||
desc.AdjustToPen = (float)(-translationX);
|
||||
@@ -414,6 +500,101 @@ namespace GlitchyEngine.Renderer.Text
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
void TestMSDF()
|
||||
{
|
||||
GlyphDescriptor desc = scope .();
|
||||
desc.GlyphIndex = FreeType.Get_Char_Index(_face, 'A');
|
||||
|
||||
var v = _range;
|
||||
|
||||
_range = 4.0f;
|
||||
_geometryScaler = 1.0f;
|
||||
|
||||
Calculate(ref desc);
|
||||
|
||||
// prepare shape
|
||||
|
||||
double advance = 0;
|
||||
|
||||
Shape shape;
|
||||
msdfgen.LoadGlyph(out shape, ref _face, 36, out advance);
|
||||
|
||||
bool b = msdfgen.ResolveShapeGeometry(shape);
|
||||
|
||||
if(!b)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
shape.Normalize();
|
||||
|
||||
var bounds = shape.GetBounds();
|
||||
|
||||
//shape.ReverseIfNeeded(bounds);
|
||||
|
||||
msdfgen.EdgeColoringSimple(shape, 3.0);
|
||||
|
||||
// prepare projection
|
||||
msdfgen.Projection projection = .();
|
||||
projection.ScaleX = _geometryScaler;
|
||||
projection.ScaleY = _geometryScaler;
|
||||
|
||||
projection.TranslationX = desc.TranslationX;
|
||||
projection.TranslationY = desc.TranslationY;
|
||||
|
||||
int bufferX = desc.Width;
|
||||
int bufferY = desc.Height;
|
||||
|
||||
using(Bitmap<ColorRGB, const 1> bitmap = .((.)bufferX, (.)bufferY))
|
||||
{
|
||||
MSDFGeneratorConfig config = .();
|
||||
|
||||
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, projection, _range, config);
|
||||
|
||||
// Check if we have to invert
|
||||
/*
|
||||
Line 1039
|
||||
// Get sign of signed distance outside bounds
|
||||
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
|
||||
double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
|
||||
orientation = distance <= 0 ? KEEP : REVERSE;
|
||||
|
||||
if (invert)
|
||||
{
|
||||
invertColor<3>(bitmap);
|
||||
}
|
||||
*/
|
||||
|
||||
uint8[] pixels = new:ScopedAlloc! uint8[desc.Width * desc.Height * 4];
|
||||
|
||||
//int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue);
|
||||
uint8 ToInt8(float f) => (.)Math.Clamp(255 * f, uint8.MinValue, uint8.MaxValue);
|
||||
|
||||
for(int y = 0; y < desc.Height; y++)
|
||||
for(int x = 0; x < desc.Width; x++)
|
||||
{
|
||||
ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x];
|
||||
|
||||
int index = ((desc.Height - y - 1) * desc.Width + x) * 4;
|
||||
|
||||
pixels[index + 0] = ToInt8(pixel.Red);
|
||||
pixels[index + 1] = ToInt8(pixel.Green);
|
||||
pixels[index + 2] = ToInt8(pixel.Blue);
|
||||
|
||||
pixels[index + 3] = uint8.MaxValue;
|
||||
}
|
||||
|
||||
LodePng.LodePng.Encode32File("testA.png", pixels.CArray(), (.)desc.Width, (.)desc.Height);
|
||||
|
||||
//_atlas.SetData<Color>((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
|
||||
// (.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z);
|
||||
}
|
||||
|
||||
_range = v;
|
||||
}
|
||||
*/
|
||||
|
||||
void GenerateMSDF(GlyphDescriptor desc)
|
||||
{
|
||||
// prepare shape
|
||||
@@ -423,11 +604,13 @@ namespace GlitchyEngine.Renderer.Text
|
||||
Shape shape;
|
||||
msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance);
|
||||
|
||||
msdfgen.ResolveShapeGeometry(shape);
|
||||
|
||||
shape.Normalize();
|
||||
|
||||
var bounds = shape.GetBounds();
|
||||
|
||||
shape.ReverseIfNeeded(bounds);
|
||||
//shape.ReverseIfNeeded(bounds);
|
||||
|
||||
msdfgen.EdgeColoringSimple(shape, 3.0);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using FreeType;
|
||||
using System.Diagnostics;
|
||||
using GlitchyEngine.Math;
|
||||
using System.Collections;
|
||||
using static FreeType.HarfBuzz;
|
||||
|
||||
using internal GlitchyEngine.Renderer.Text;
|
||||
using internal GlitchyEngine.Renderer;
|
||||
@@ -109,6 +110,87 @@ namespace GlitchyEngine.Renderer.Text
|
||||
//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)
|
||||
{
|
||||
@@ -182,7 +264,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
// Increase depth for the next glyph
|
||||
//depthInt--;
|
||||
}
|
||||
|
||||
*/
|
||||
Renderer2D.Flush();
|
||||
|
||||
// TODO: not good!
|
||||
|
||||
Vendored
+1
-1
Submodule GlitchyEngine/vendor/freetype updated: c539bd7e13...f41e3fd511
Vendored
+1
-1
Submodule GlitchyEngine/vendor/msdfgen updated: ac77962905...9f19e63a51
@@ -35,11 +35,34 @@ PS_Input VS(VS_Input input)
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
in vec2 texCoord;
|
||||
out vec4 color;
|
||||
uniform sampler2D msdf;
|
||||
uniform vec4 bgColor;
|
||||
uniform vec4 fgColor;
|
||||
|
||||
float median(float r, float g, float b) {
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float ScreenPxRange(float2 texcoord) {
|
||||
void main() {
|
||||
vec3 msd = texture(msdf, texCoord).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPxRange()*(sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
color = mix(bgColor, fgColor, opacity);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
float median(float r, float g, float b)
|
||||
{
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float ScreenPxRange(float2 texcoord)
|
||||
{
|
||||
float2 screenTexSize = 1.0f / fwidth(texcoord);
|
||||
return max(0.5f * dot(UnitRange, screenTexSize), 1.0f);
|
||||
}
|
||||
@@ -54,4 +77,17 @@ float4 PS(PS_Input input) : SV_Target0
|
||||
return float4(input.Color.rgb, opacity * input.Color.a);
|
||||
}
|
||||
|
||||
/*
|
||||
// 2D
|
||||
float4 PS(PS_Input input) : SV_Target0
|
||||
{
|
||||
float3 msd = Texture.Sample(Sampler, input.TexCoord).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPixelRange*(sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
|
||||
return float4(input.Color.rgb, opacity * input.Color.a);
|
||||
}
|
||||
*/
|
||||
|
||||
#effect[VS=VS, PS=PS]
|
||||
@@ -69,9 +69,11 @@ namespace Sandbox
|
||||
var japanese = new Font("C:\\Windows\\Fonts\\YuGothM.ttc", 64, true, '\0', 1);
|
||||
var emojis = new Font("C:\\Windows\\Fonts\\seguiemj.ttf", 64, true, '😂' - 10, 1);
|
||||
var mathstuff = new Font("C:\\Windows\\Fonts\\cambria.ttc", 64, true, 'α', 1);
|
||||
var cascadiaCode = new Font("C:\\Windows\\Fonts\\CascadiaCode.ttf", 32, true, 'A', 1);
|
||||
fonty.Fallback = japanese..ReleaseRefNoDelete();
|
||||
japanese.Fallback = emojis..ReleaseRefNoDelete();
|
||||
emojis.Fallback = mathstuff..ReleaseRefNoDelete();
|
||||
mathstuff.Fallback = cascadiaCode..ReleaseRefNoDelete();
|
||||
|
||||
_textureViewer = new TextureViewer();
|
||||
|
||||
@@ -112,7 +114,7 @@ namespace Sandbox
|
||||
|
||||
Renderer2D.DrawCircle(Vector3(-2, -2, -2f), Vector2(1), .GreenYellow);
|
||||
|
||||
FontRenderer.DrawText(fonty, "Hallo! gjy", 0, 0, 64, .White, .White);
|
||||
FontRenderer.DrawText(fonty.Fallback.Fallback.Fallback.Fallback, "Hallö! gjy ÄAwww www", 0, 0, 64, .White, .White); // Hallo! gjy
|
||||
|
||||
Renderer2D.EndScene();
|
||||
}
|
||||
@@ -143,6 +145,7 @@ namespace Sandbox
|
||||
|
||||
ImGui.End();
|
||||
|
||||
//_textureViewer.ViewTexture(fonty.Fallback.Fallback.Fallback.Fallback.[Friend]_atlas);
|
||||
_textureViewer.ViewTexture(fonty.[Friend]_atlas);
|
||||
|
||||
_context.SetRenderTarget(null);
|
||||
|
||||
Reference in New Issue
Block a user