[OpenGL 4.5] Text Rendering

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
xq
Establishment
Beiträge: 1589
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

[OpenGL 4.5] Text Rendering

Beitrag von xq »

Hey!

Ich bastel ja grade an einer Engine und habe mittlerweile auch Font Rendering via FreeType implementiert.

Da meine Grafikkarte etwas zickig ist, wenn ich mehr als einen Monitor verwende, erhalte ich folgende Renderzeiten:

Mit Font-Rendering:

Code: Alles auswählen

Render Frame: 20.8847 ms ( 47 fps) ← was mir mein Framework als letzte Frame Time liefert
Update Frame: 20.8930 ms ( 47 fps)
Render Time:  21.5780 ms ← Mit Stopwatch zwischen "begin(draw) und end(draw)
Update Time:   0.6297 ms
Wenn ich das Font Rendering ausschalte, steigt die Performance um gute 20%:

Code: Alles auswählen

Render Frame: 16.9969 ms ( 58 fps)
Update Frame: 16.8458 ms ( 59 fps)
Render Time:   9.2721 ms
Update Time:   3.7226 ms
Um den Text zu rendern, verwende ich folgenden Code:

Code: Alles auswählen

var text = tw.Text;
var font = tw.Font ?? theme.DefaultFont;

font.SetPixelSize(tw.FontSize);

this.glyphTexture.BindTo(TextureUnit.Texture0);

var size = font.MeasureString(text);

var px = x + (float)tw.Padding;
var py = y + (float)tw.Padding;

// Adjust to alignment like a boss!
py += 0.5f * (((int)tw.TextAlignment & 0xF0) >> 4) * (h - size.Y - 2 * (float)tw.Padding);
px += 0.5f * (((int)tw.TextAlignment & 0x0F) >> 0) * (w - size.X - 2 * (float)tw.Padding);

for (int i = 0; i < text.Length; i++)
{
	char c = text[i];
	if (c == '\n')
	{
		py += font.Size;
		px = x + (float)tw.Padding;
	}
	// Ignore all other control characters
	if (char.IsControl(c))
		continue;

	if (char.IsSurrogate(c))
	{
		font.LoadChar((uint)char.ConvertToUtf32(c, text[++i]));
	}
	else
	{
		font.LoadChar(c);
	}
	var slot = font.GlyphSlot;

	var bitmap = slot.Bitmap;
	if (bitmap.Width > 0)
	{
		GL.ActiveTexture(TextureUnit.Texture0);
		GL.BindTexture(TextureTarget.Texture2D, this.glyphTexture.ID);

		GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
		GL.TexImage2D(
			TextureTarget.Texture2D,
			0,
			PixelInternalFormat.Rgba8,
			bitmap.Width,
			bitmap.Rows,
			0,
			PixelFormat.Alpha,
			PixelType.UnsignedByte,
			bitmap.Buffer);

		effect["uRectangle"].SetValue(new Vector4(
			(int)(px + slot.BitmapLeft),
			(int)(py + tw.FontSize - slot.BitmapTop),
			bitmap.Width,
			bitmap.Rows));
		effect["uTextureConfig"].SetValue(new Vector4(
			0, bitmap.Width,
			0, bitmap.Rows
		));

		GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
	}
	px += slot.Advance.X.ToSingle();
	py += slot.Advance.Y.ToSingle();
}
Als Vertex-Daten ist ein einzelnes Quad gebunden, als Shader ein einfacher Pixel-Shader, welcher eine Textur entweder direkt kopieren oder aber als Patch9-Struktur rendern kann...

Was kann ich alles verbessern, um die Performance des Text Renderings zu steigern?

Meine aktuellen Ideen sind folgende:
  • Für jeden verwendeten Glyphen eine prerendered Textur cachen
  • Komplette Strings auf eine einzige Textur rendern mit glCopyImageSubData oder glTextureSubImage2D, anschließend rendern
  • Komplett gerenderte Strings cachen
Habt ihr noch andere Ideen? Erfahrung mit den oben genannten Ideen?

Lohnt sich SDF-Font-Rendering a'la Valve ( http://www.valvesoftware.com/publicatio ... cation.pdf ) oder wäre das für UI eher ein Holzweg?

Grüße
Felix
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5013
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [OpenGL 4.5] Text Rendering

Beitrag von Schrompf »

Das Auflösen der Kurven direkt im Shader ist ne coole Sache für jede Art von Font Rendering, denke ich. Aber da Deine GPU noch aus Holz ist, solltest Du das vielleicht erstmal sein lassen.

Allgemein: verstehe ich den Code richtig, dass Du für jeden Glyph in jedem Frame den Buchstaben neu ausrendern lässt, in eine Textur hochlädst und mit einem DrawCall pinselst? Really? Mach Dir einen Texturatlas, wie ich schon etwa fünfhundert mal im Chat empfohlen habe, und render alle Glyphen einmal vorab. Dann kannst Du auch alle Glyphen in einen DrawCall batchen und - wenn Du wirklich die letzten x µs rausholen willst, den VertexBuffer pro Text irgendwie über Frames hinweg cachen.

Alternativ, und das machen auch einige, kannst Du aber auch den kompletten Text in mit Freetype ausrendern lassen und als Textur cachen. Sollte für Button-Beschriftungen taugen, aber ist natürlich für jede Art von dynamischem Textinhalt (z.B. die FpS-Anzeige) fast genauso schlecht wie die aktuelle Version. Nur fast, weil Du immerhin den gesamten Text als einen Image Transfer und einen DrawCall machst.

Ich benutze die Texturatlas-DynamicBatch-Methode von oben und bin damit wieder GPU-limitiert, selbst auf einer Geforce1070. Wobei halt, das wäre gelogen. Tatsächlich nachgemessen habe ich es nur mit einer Geforce 770
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: [OpenGL 4.5] Text Rendering

Beitrag von Spiele Programmierer »

Vorweg, Schrompf hat völlig recht mit dem was er sagt. Nicht ständig die Texturdaten erneut hochzuladen und einen Texturatlas (d.h. alle einzelnen Bildchen in eine Textur packen) zu verwenden, ist absolut unerlässlich für akzeptable Performance.

Ich habe in der Vergangenheit recht gute Erfahrung mit SDF-Fonts gemacht. Ich hatte nie die Notwendigkeit die Performance im Detail zu vergleichen, aber prinzipiell sollte das kaum langsamer sein als "nicht SDF"-Fonts. Ich würde mir dahin gehend keine Sorgen machen. Ich halte es für wahrscheinlich, dass es sogar schneller ist, weil man bei großen Textabschnitten mit deutlich niedrigerer Texturauflösung auskommt und das sollte gut für den Cache sein. Der große Vorteil von SDF ist, dass man jeden Buchstaben nur einmal im Texturatlas abspeichern muss und man den Text dann in Echtzeit noch bedenkenlos beliebig skalieren, rotieren oder fett machen kann. Des Weiteren kann man in Shadern sehr leicht viele visuell ansprechende Effekte umsetzen und auch animieren.

Der Nachteil gegen über komplizieren Techniken ist, dass starke perspektivische Transformation beim klassischen SDF zu Unschärfeproblemen führt.
Außerdem ist sehr kleiner Text nicht optimal scharf, weil in den SDFs natürlich nicht auf Hinting-Informationen der Fonts zurückgegriffen werden kann. Wenn du also sehr kleine Textelemente in der GUI verwenden möchtest, würde ich von der Technik eher abraten.

Mein erster Ansatz zum Erstellen der SDFs war, die Buchstaben zuerst mit sehr hoher Auflösung in Freetype zu rendern und dann anschließend ein SDF daraus zu berechnen, welches dann runter skaliert wird. Wenn die Font das mitmacht (Manche Fonts, besonders die die man so im Internet findet, haben nicht ganz einwandfreie Konturen und ohne die Ganzen Tricks die z.B. Freetypes Rasterizer mitbringt, gibt es dann Artefakte im SDF), kann man aber die SDFs auch direkt aus den Vektordaten der Font extrahiert und erreicht so wesentlich bessere Ergebnisse (und auch deutlich effizienter). Das kann man entweder selbst schreiben oder man baut auf etwas Fertiges auf. Ich habe das hier verwendet: https://github.com/Chlumsky/msdfgen (MIT-Lizenz)
Die genannte Bibliothek kann auch noch Multi-Channel-SDFs generieren, diese sollen auch bei Skalierung ihre scharfen Ecken erhalten. Das hat bei mir aber leider auf irgendeinem Grund nicht richtig funktioniert, und letzendlich waren normale SDF dann auch gut genug.

Ich habe mal ein paar Beispielbilder angehängt, die ich noch so rumliegen habe. Im zweiten Bild sieht, sieht man beispielhaft zwei Effekte, die man sehr einfach mit einem sehr Shader umsetzen kann, der Zugriff auf die Abstandsdaten hat. Interessant ist auch, dass in allen Beispielen die Auflösung der Glyphen in der Textur identisch ist. Wie man sieht, hat man relativ großen Spielraum beim Skalieren. Unten sieht man die erstellten SDF-Texturen. Man kann auch erkennen, dass eine Schriftart "Denise Handwriting" leider ein paar Artefakte an manchen Ecken hat.
Der Texturatlas mit den SDFs wird beim Starten der Anwendung aus der Font-Datei erzeugt. Das geht praktisch sofort, ließe sich aber auch sehr einfach parallelisieren.
Dateianhänge
capture.png
Text.png
smurfer
Establishment
Beiträge: 208
Registriert: 25.02.2002, 14:55

Re: [OpenGL 4.5] Text Rendering

Beitrag von smurfer »

Vieles wurde ja schon gesagt, da ich mich vor kurzem auch mal mit dem Thema beschäftigt hatte, einfach ein paar Links zum Durchstöbern, vielleicht ist etwas Neues oder Hilfreiches für dich dabei:
http://stackoverflow.com/questions/5262 ... ersion-4-1
https://stackoverflow.com/questions/207 ... -for-a-gui
http://wdobbie.com/post/gpu-text-render ... -textures/

Ich persönlich finde stb_truetype als Alternative zu Freetype recht nett, da Single-Header:
https://github.com/nothings/stb
https://github.com/0xc0dec/stb-truetype-opengl-examples Beispiel zur Verwendung mit OpenGL
http://digestingduck.blogspot.de/2009/0 ... stash.html Beispiel Fontstash als Bibliothek mit Cache
Benutzeravatar
Jonathan
Establishment
Beiträge: 2529
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [OpenGL 4.5] Text Rendering

Beitrag von Jonathan »

Nur der Vollständigkeit halber: Ich hatte mir damals auch eine recht simple aber wohl durchaus effiziente Methode gebastelt:

https://zfx.info/viewtopic.php?f=11&t=3557&p=44749

Es wird für jede Schriftgröße am Anfang ein Texturatlas gebaut (und die Glyphen recht effizient zusammen gepackt) und für jeden Text den man rendern will dann ein Vertexbuffer erstellt, den man dann in einem Rutsch rendern kann.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
antisteo
Establishment
Beiträge: 926
Registriert: 15.10.2010, 09:26
Wohnort: Dresdem

Re: [OpenGL 4.5] Text Rendering

Beitrag von antisteo »

Wenn du schönen Textsatz haben willst, solltest du mindestens komplette Wörter von Freetype rendern lassen. Damit das ganze Pixel-perfect aussieht, für jede Auflösung in exakter Pixelhöhe. (angenommen, du hast eine Schriftgröße Normal und eine für Überschriften). Bei gwX haben wir tatsächlich Wörter gerendert (und auch gecacht glaube ich). Wenn du komplette Textblöcke rendern kannst, ist das auch eine Lösung.

Einzelne Buchstaben rendern sieht scheiße aus, da jeder Buchstabe zu jedem anderen Buchstaben einen individuellen Abstand hat, der auch im Subpixel-Bereich liegen kann. Eine Ausnahme sind Monospace-Schriften.
http://fedoraproject.org/ <-- freies Betriebssystem
http://launix.de <-- kompetente Firma
In allen Posts ist das imo und das afaik inbegriffen.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2529
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [OpenGL 4.5] Text Rendering

Beitrag von Jonathan »

Beziehst du dich damit nur auf Kerning[1], oder auch auf mehr? In dem Link den ich gepostet habe, wird Kerning bereits beachtet, was ziemlich simpel ist, da Freetype dir eine Tabelle gibt, in der für jede Buchstabenkombination der richtige Offset steht[2]. Aber da ich ein laienhaftes Interesse an Typografie habe, wäre es nett zu hören, was man außer Kerning noch tut, damit es schönes aussieht (und was dementsprechend der Vorteil wäre, ganze Wörter von freetype rendern zu lassen).

[1] https://de.wikipedia.org/wiki/Unterschn ... ypografie)
[2] CursorPosition.x += ftgl::texture_glyph_get_kerning(glyph, wtext);
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: [OpenGL 4.5] Text Rendering

Beitrag von Spiele Programmierer »

Meines Wissens gibt es keine angemessenen Kerning-Funktionen in Freetype. Und man kann auch keine kompletten Wörter einfach so von FreeType rendern lassen. Es gibt FT_Get_Kerning, aber das unterstützt OpenType-Kerning nicht und es wird auch generell immer eher von der Funktion abgeraten.

Wenn man Wert auf richtiges Kerning legt, sollte man HarfBuzz verwenden. Das ist tatsächlich auch nicht besonders schwer zu verwenden und bring auch gleich die Unterstützung von solchen Sachen wie Combining Characters. Man steckt in eine HarfBuzz-Funktion im Prinzip einfach die Unicode Codepoints und erhält dann die relative Position von den einzelnen Glyphen der Font. Wenn man erstmal geschaft hat die Lib einzubinden (immer schwierig mit C++), kann man das ganze in gut in 50 Zeilen wegkapseln. Das Bild mit dem arabischen Text das ich zuletzt angehängt habe, wurde auch mit Hilfe von HarfBuzz-Kerning gerendert. Für Arabisch muss man zusätzlich noch den Unicode BiDi-Algorithmus beachten (Es gibt auch fertige Libs dafür). Tatsächlich steckt da überall der Teufel im Detail. Allerdings muss man sich auch fragen wie gut es wirklich sein muss.

Die Dokumentation von HarfBuzz ist leider sehr dünn bis praktisch nicht vorhanden.
Dieser Beispielcode war für mich sehr nützlich: https://github.com/anoek/ex-sdl-cairo-f ... harfbuzz.c
Benutzeravatar
xq
Establishment
Beiträge: 1589
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: [OpenGL 4.5] Text Rendering

Beitrag von xq »

Vielen dank für die ganzen Antworten! Ich werd mir wohl harfbuzz mal genauer angucken, hoffentlich gibt es dafür eine C#-Library.

Und ein Texturatlas + Textglyphen cachen und nur bei geändertem Text regenerieren klingt auch sinnvoll
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Antworten