1 Punkte von GN⁺ 2024-11-11 | 1 Kommentare | Auf WhatsApp teilen
  • Wenn in einem Render-Pass schnell Debug-Meldungen ausgegeben werden müssen, ist die Vorbereitung eines Font-Atlas oft zu aufwendig; daher ist ein Ansatz nützlich, der Text nur mit Fragment-Shader-Konstanten zeichnet
  • Glyphen werden als 8x16-Pixel-Bitmaps dargestellt, und 96 druckbare ASCII-Zeichen werden in einem uvec4-Array mit 1536 Byte gespeichert, das der Shader direkt abfragt
  • Die Font-Daten lassen sich aus einer PSF1-Terminalschrift erzeugen, indem nach dem Überspringen des 4-Byte-Headers und des 512-Byte-Bereichs für nicht druckbare Glyphen die nächsten 96 Glyphen extrahiert werden
  • Das Rendering verwendet eine Per-Instance-Struktur mit uint32_t-Wörtern zu je 4 Zeichen sowie Position und Skalierung und zeichnet den gesamten Text mit einem einzigen instanziierten Draw Call
  • Da die Technik auf einfache Debug-Ausgabe fokussiert ist, müssen Einschränkungen wie 4-Zeichen-Padding, das Füllzeichen \0 mit discard und Endianness-Korrekturen gemeinsam in Anwendung und Shader behandelt werden

Shader-Konstanten statt Font-Atlas

  • Übliches Text-Rendering rendert die möglichen Glyphen in einen Font-Atlas, bindet diesen als Textur und zeichnet dann für jede Glyphe die passenden Dreiecke
  • Auch imgui und stb_truetype nutzen diesen Ansatz, doch für schnelle Debug-Meldungen kann der Vorbereitungsaufwand lästig sein
  • Eine Alternative besteht darin, die Daten des Font-Atlas als Ganzzahlkonstanten im Fragment-Shader zu speichern
  • Ganzzahlen lassen sich wie Bitmaps verwenden: Die xy-Position des Fragments wird auf eine bestimmte Bit-Position abgebildet, und je nachdem, ob dieses Bit gesetzt ist, wird Vorder- oder Hintergrundfarbe ausgegeben

8x16-Bitmap-Glyphen und ASCII-Tabelle

  • Da ein Byte nur eine einzelne Pixelzeile darstellen kann, werden pro Glyphe 16 Byte verwendet, um besser lesbare Glyphen zu erhalten
  • Eine Glyphe bildet damit eine 8x16-Pixel-Leinwand, und ein einzelnes uvec4 in GLSL kann die benötigten 16 Byte exakt aufnehmen
  • Für 96 druckbare ASCII-Glyphen ergibt sich eine Gesamtdatenmenge von 1536 Byte
  • Das Array font_data[96] verwendet als Index den ASCII-Wert minus 0x20
    • Ziel sind die druckbaren ASCII-Glyphen beginnend bei 0x20 SPACE
    • Im Beispielcode werden aus Platzgründen nur einige Einträge gezeigt
  • Die vollständige Bitmap-Tabelle ist im Island source code enthalten

Bitmaps aus einer PSF1-Schrift extrahieren

  • Die benötigte Bitmap-Kodierung entspricht fast exakt der einer Terminalschrift im PSF1-Format
  • Das Extrahieren der Daten aus einer PSF1-Terminalschrift ist einfach
    • Die Font-Datei in einem Hex-Editor wie ImHex öffnen
    • Den 4-Byte-Header überspringen
    • Den 512-Byte-Abschnitt mit nicht druckbaren Glyphen überspringen
    • Die nächsten 96 Glyphen, also 1536 Byte, per „Copy as → C Array“ exportieren
  • Das extrahierte char-Array kann zu einem uint-Array angepasst und anschließend in uvec4-Einheiten gruppiert werden
  • Wenn rohe char-Werte direkt zu uint zusammengesetzt werden, wird die Endianness vertauscht; beim Sampling wird das wieder korrigiert
  • Die Quelldaten der verwendeten Pixel-Schrift stammen aus Scott Fials freier Pixel-Font Tamsyn

Aufbau eines einzelnen instanziierten Draw Calls

  • Das Text-Rendering wird mit einem einzigen instanziierten Draw Call ausgeführt
  • Der Draw Call verwendet zwei Attribut-Streams
    • Der Per-Draw-Stream enthält nur die Informationen, die zum Zeichnen eines normalen Quads nötig sind
    • Der Per-Instance-Stream enthält den Positionsoffset auf dem Bildschirm und den auszugebenden Text
  • Für den Positionsoffset werden x- und y-Floats genutzt; im verbleibenden Float-Platz kann der Font-Scale-Wert untergebracht werden
  • In Vulkan müssen alle Komponenten eines Vertex-Output-Bindings dieselben Interpolationseigenschaften haben, daher lassen sich vec3 und uint innerhalb eines Bindings nicht sauber mischen
  • Der Text wird in 4-Zeichen-Einheiten in uint32_t gepackt
    • Der kleinste Standarddatentyp für Vertex-Attribute ist meist 32 Bit, daher werden 4 Zeichen auf einmal gespeichert
    • Die Nachrichtenlänge muss durch 4 teilbar sein
    • Fehlende Zeichen werden mit \0 aufgefüllt
  • Die Per-Instance-Daten werden als word_data-Struktur dargestellt
    • pos_and_scale[3]: xy-Position und Skalierung
    • word: die vier auszugebenden Zeichen
  • Die Anwendung zerlegt die Nachricht in 4-Zeichen-Blöcke, wandelt jeden Block in ein uint32_t um und sammelt diese zusammen mit dem Positionsoffset in einem word_data-Array
  • Beim Rendering wird dieses Array als Per-Instance-Binding an die Debug-Text-Zeichenpipeline gebunden, und es werden so viele Instanzen wie Quads gezeichnet

Position und Zeichen im Vertex-Shader übergeben

  • Der Vertex-Shader gibt gl_Position, das zu rendernde word und einen Wert entsprechend den Texturkoordinaten aus
  • gl_Position nutzt die Per-Instance-Daten pos_and_scale, um die Dreiecks-Vertices im NDC-Koordinatensystem auf dem Bildschirm zu platzieren
  • Das zu rendernde word wird als Eingabeattribut vom Typ uint unverändert an den Fragment-Shader weitergereicht
    • Damit keine Interpolation stattfindet, wird der Qualifier flat verwendet
  • Die Texturkoordinaten werden aus gl_VertexIndex synthetisiert
    • 12 >> gl_VertexIndex & 1 erzeugt die Sequenz 0, 0, 1, 1
    • 9 >> gl_VertexIndex & 1 erzeugt die Sequenz 1, 0, 0, 1
    • Damit entstehen ohne Verzweigungen die UV-Koordinaten (0,1), (0,0), (1,0), (1,1)
  • Der Vertex-Shader übernimmt außerdem Vorder- und Hintergrundfarbe aus den Per-Instance-Daten und gibt sie an den Fragment-Shader weiter

Glyphen-Sampling im Fragment-Shader

  • Damit der Fragment-Shader den Text rendern kann, benötigt er drei Informationen
    • die interpolierten UV-Koordinaten des Fragments
    • die auszugebenden Zeichendaten in_word
    • das Glyphen-Bitmap-Array font_data
  • Die UV-Koordinaten liegen im normalisierten Float-Bereich von vec2(0.f,0.f) bis vec2(1.f,1.f), während die Pixelkoordinaten der Glyphe von uvec2(0,0) bis uvec2(7,15) reichen
  • Das gesamte 4-Zeichen-Word wird als Bereich von 32 Pixeln Breite und 16 Pixeln Höhe behandelt
    • uv.xy * vec2(8 * WORD_LEN, 16) wird mit floor auf Word-Pixelkoordinaten quantisiert
    • Der Koordinatenbereich wird auf uvec2(0..31, 0..15) begrenzt
    • Mit word_pixel_coord.x / 8 wird ermittelt, zu welchem der vier Zeichen der Bereich gehört
    • Mit word_pixel_coord.x % 8 wird die x-Koordinate innerhalb der Glyphe bestimmt
  • Der Zeichencode wird in einen Index für font_data umgewandelt
    • Da die erste Glyphe 0x20 SPACE ist, wird printable_character - 0x20 als Offset verwendet
    • Mit diesem Offset wird die Glyphen-Bitmap als uvec4 geladen
  • Über die y-Koordinate wird mit glyph_pixel_coord.y / 4 ein bestimmtes uint innerhalb des uvec4 ausgewählt
    • Dieses uint enthält die Pixeldaten von 4 Zeilen
    • Da die aus ImHex exportierten char-Werte direkt zu uint verkettet wurden, ist die Zeilenreihenfolge vertauscht
    • Das wird mit einer Indizierung der Form (8*(3-(glyph_pixel_coord.y)%4)) wieder korrigiert
  • Für die x-Koordinate wird das Bit mit 7-glyph_pixel_coord.x gewählt
    • Das höchstwertige Bit eines Bytes ist am höchsten Index gespeichert, daher ist für die Abbildung von links nach rechts eine umgekehrte Indizierung nötig
  • Mit dem finalen current_pixel wird die Farbe per mix(background_colour, foreground_colour, current_pixel) bestimmt

Kurze Strings und Behandlung des Füllzeichens

  • Wenn die Stringlänge nicht durch 4 teilbar ist, füllt die Anwendung die fehlenden Stellen mit \0 auf
  • Der Fragment-Shader prüft, ob das auszugebende Zeichen \0 ist
  • Trifft er auf ein Füllzeichen \0, zeichnet er auch keinen Hintergrund und führt discard aus
  • So bleibt die 4-Zeichen-Packung erhalten, und dennoch lassen sich kurze Strings ausgeben

Nutzungsform und Speicherort des Codes

  • Im Island-Projekt kann über le::DebugPrint Debug-Text auf dem Bildschirm ausgegeben werden
  • Der vollständige Fragment-Shader ist auf github verfügbar
  • Das Codebeispiel zeigt, wie String-Daten übergeben werden, um eine Meldung in der Form "That's all, %s" auf dem Bildschirm auszugeben

1 Kommentare

 
GN⁺ 2024-11-11
Hacker-News-Kommentare
  • Wenn man es selbst ausprobieren möchte, kann man einfach die Arithmetik nachvollziehen. Es ist extrem einfach, das von Grund auf in ShaderToy zu implementieren, und wenn man auf so etwas steht, ist es auch ein netter Samstagmorgen-Zeitvertreib.
    Es macht Spaß, es von Grund auf zu bauen, aber falls man einen Starthinweis braucht, gibt es ein Beispiel, das ich gerade erstellt habe: https://www.shadertoy.com/view/Mc3cW2
    Es gibt auch viele clevere Text-Hacks von anderen Leuten, etwa das Matrix-Beispiel mit unter 300 Zeichen https://www.shadertoy.com/view/llXSzj oder den grünen CRT-Anzeigeeffekt https://www.shadertoy.com/view/XtfSD8

    • Jedes Mal, wenn ich Immediate-Mode-Text-Rendering ausprobiert habe, habe ich es nie geschafft, Text in kleinen Größen gut aussehen zu lassen. Schon im ersten ShaderToy sieht man Artefakte, wenn man bei vec2(30, -30) die 30 in 300 ändert.
      Ich frage mich, ob es dafür einen guten Trick gibt. Bei mir hat mehrfaches Sampeln von Texturen im Fragment-Shader am besten funktioniert, aber selbst das war nicht so gut wie moderne Verfahren.
    • Ich entwickle Unity-Spiele, und hier fühlt es sich wirklich so an, als würde alles rückwärtslaufen. Unity hat vor ein paar Jahren das beste Schrift-Rendering-Tool, das von einer einzelnen Person gebaut wurde, gekauft und als Standardfunktion integriert; danach kam die Entwicklung fast zum Stillstand, und der konkurrierende Markt für Schrift-Rendering ist ebenfalls abgestorben.
      Vor Kurzem wollte ich eine App bauen, die wie eine native Konsolenschrift aussieht, und ich musste mehr als zwei Stunden herumprobieren, nur um ungefähr 90 % zu erreichen.
    • Etwas off topic, aber ein interessantes Beispiel: Ein Matrix-Effekt in HTML/CSS/JS passt in 1024 Byte: https://codegolf.stackexchange.com/a/17414
  • Es ist einfallsreich und herrlich hacky. Eigentlich gilt das für fast alle 3D-Rendering-Techniken, aber sofern das Ergebnis nicht absichtlich ein altes elektronisches Schwarzes Brett nachbilden soll, ist es nicht wirklich schön.
    Mit mehr Bits könnte man es verbessern, aber lange bevor es wirklich gut aussieht, wird man wahrscheinlich einen einfacheren Weg finden, alle Bits zu setzen. Am Ende landet man wieder am Ausgangspunkt, denn es gibt kaum eine effizientere Lösung, als die Zeichen in einem Zeichenprogramm als Schwarzweiß-Pixel zu erstellen und als Textur zu speichern.
    Wenn man wissen will, wie moderne 3D-Rendering-Engines Text normalerweise rendern, sollte man nach SDF-Text und verwandten Verfahren wie MSDF suchen. Dabei wird in einem Vorverarbeitungsschritt mit einem traditionellen Texture-Atlas ein Signed-Distance-Field-Atlas erzeugt.

    • Passend zu „wieder am Ausgangspunkt“: Falls du es noch nicht gesehen hast, ist das Paper von 1968 On the Design of Display Processors lesenswert: http://cva.stanford.edu/classes/cs99s/papers/myer-sutherland...
      Das Paper handelt zwar von Hardware, aber auch Software hat ihre Wiedergeburten.
    • Verfahren wie SDF-Text oder MSDF sind inzwischen mindestens eine Generation alt. Heute verwendet fast jeder Ansätze wie https://sluglibrary.com, bei denen die Bezier-Kurven der Schrift direkt im Shader rasterisiert werden.
      Ich habe einmal eine sehr grundlegende Version dieses Konzepts gebaut: https://www.shadertoy.com/view/sdXBDs
    • Zum Beispiel für Debug-Text in Situationen, in denen Textur-Uploads nicht richtig funktionieren, ist das ziemlich clever. Der Vergleich des Originals, das Sprite-Sheet mit manuellem Buchdruck im 16. Jahrhundert gleichsetzt, ist zwar niedlich, aber realistisch betrachtet brauchte ein Druckgehilfe eine Stunde, um Metalllettern zu setzen, während das Hochladen eines Sprite-Sheets auf die GPU keine 10 ms dauert und es danach beliebig oft gesetzt werden kann.
      Das soll nicht heißen, dass es kein cooler Trick ist — es ist tatsächlich ein cooler Trick.
  • Es gibt auch die Möglichkeit, Text als Mesh zu rendern. TextMeshPro geht noch einen Schritt weiter und verwendet Signed Distance Fields, um beliebige Skalierung zu unterstützen.
    https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/...

    • Noch weiter geht die direkte Auswertung von Schriftkurven auf der GPU, die unabhängig von Skalierung oder Perspektive hohe Qualität liefern kann. Effizient umzusetzen ist sehr schwierig, aber möglich ist es.
      Beispiel: https://sluglibrary.com
      Meshes und SDF sind auf der GPU-Seite deutlich einfacher, können aber bei sehr starker Vergrößerung an Präzision verlieren, und wenn man Meshes zu stark verkleinert, kann Aliasing auftreten.
  • Sehr cool. Ein Performance-Vergleich mit dem „traditionellen“ Texturansatz wäre interessant.
    Bei modernen GPUs scheint die Antwort auf die Frage „Wie ist die Performance?“ bei solchen einfachen Aufgaben meistens „Sie reicht“ zu sein.

    • Auf „Wie ist die Performance?“ hätte ich gern die Antwort: „VSCode frisst nicht länger weitere Hunderte MB bis mehrere GB meines VRAM.“
  • Sebastian Lague hat ein gutes Video über verschiedene Schrift-Rendering-Techniken gemacht.
    https://youtu.be/SO83KQuuZvg

  • Ich habe einmal eine ähnliche Technik verwendet und die kompletten Schriftdaten direkt in den Quellcode des Fragment-Shaders eingebettet. Dann kann man mit snprintf direkt in einen GPU-Puffer schreiben, der auf die CPU gemappt ist. Mir ist klar, dass das ein riskanter Ansatz ist
    Statt einzelne Zeichen im Vertex-Shader zu zeichnen, habe ich nur ein einziges Fullscreen-Dreieck gezeichnet und gl_FragCoord statt UV-Koordinaten verwendet. Das ist nicht die effizienteste Methode, aber es war eine Debug-Funktion und in der Praxis schnell genug
    Trotz des Dateinamens wird nicht die NES-, sondern die Schrift aus dem IBM-PC-ROM verwendet. Andere 8x8-Pixel-Schriften als die „NES font“ findet man im Web
    https://github.com/rikusalminen/triangles/blob/nesfont/shade...

    • Mein liebstes Pixel-Font-Paket ist dieses hier: https://int10h.org/oldschool-pc-fonts/
    • Erst vor Kurzem habe ich erfahren, dass die „NES“-Schrift aus dem Arcade-Spiel Quiz Show von 1976 stammt. Diese Schrift wurde auch in anderen monochromen Kee/Atari-Spielen verwendet, und die Schriftdaten sind im quizshow-MAME-ROM-Set enthalten, dort aber aus irgendeinem Grund in Nibbles aufgeteilt
      Interessanterweise speicherte dieses Spiel seine Fragen und Antworten auf 8-Spur-Band
  • Cool. Man sieht nicht oft einen Text-Rendering-Algorithmus, den ich noch nicht selbst ausprobiert habe. In meinem Startup haben wir verschiedene Varianten implementiert, aber für mich wären Auflösungsunabhängigkeit und Anti-Aliasing notwendig gewesen, daher hätte mir dieser Ansatz wohl nicht geholfen
    Es lässt sich vielleicht nicht auf allgemeine Schriftdateien mit Bézier-Kurven verallgemeinern. Kurven in Pixel umzuwandeln kann schwierig sein, besonders wenn sich Glyphen selbst schneiden. Insgesamt wirkt Standard-Text-Rendering wie ein gelöstes Problem, und bei nicht standardmäßigen Anwendungsfällen sind Experimente ziemlich unerquicklich
    Konzeptionell scheint das dem Ansatz von Will Dobbie ähnlich zu sein, den ich mag, nur deutlich einfacher. Beide nehmen rohe Schriftdaten und verwenden sie direkt im Shader. Der Unterschied ist, dass hier Pixeldaten in einem Array gespeichert werden, während Will SVG-Pfaddaten als „Vektor-Textur“ speichert
    Falls es jemanden interessiert: Will hat eine coole Demo: https://wdobbie.com/warandpeace/

  • Ich hatte früher auch einmal über so etwas nachgedacht, aber ich war davon ausgegangen, dass GPUs besonders effizient beim Textur-Rendering und vergleichsweise langsam bei Bit-Operationen sind. Selbst wenn man hier etwas Speicher spart, frage ich mich, ob das tatsächlich schneller ist als die Verwendung eines Atlas
    Vielleicht ließen sich die Vorteile beider Ansätze kombinieren, indem man Bit-Packing in einer normalen Textur speichert und der Fragment-Shader das Dekodieren übernimmt

    • Diese Vorstellung ist schon ziemlich alt. Auf GPUs der letzten etwa 15 Jahre sind Texturzugriffe ungefähr 100-mal langsamer als Bit-Operationen
  • Ich kenne mich mit moderner Computergrafik kaum aus, daher die naive Frage: Ist es wirklich so teuer, kleine Texturen auf die GPU hochzuladen? Könnte man nicht einfach die komplette Zeichenkette als 2D-Textur rendern und diese Textur dann auf zwei Dreiecken anzeigen?

    • Nein, teuer ist das nicht. Bei dieser Technik geht es weniger um Performance als darum, Debug-Text so einfach wie möglich auf den Bildschirm zu bekommen. Man fügt dem Shader ein paar Daten hinzu, und schon erscheint Text
      Andernfalls muss man Code zum Erzeugen eines Font-Atlas schreiben oder einen vorhandenen Atlas finden und laden, wofür wiederum Ladecode nötig ist. Oder man muss die gesamte Nachricht in eine Textur zeichnen und das Ergebnis cachen, bis sich die Nachricht ändert
      Dazu kommen Ressourcenverwaltung und Binding, während dieser Ansatz gar keine Ressourcen benötigt. Anders gesagt: Das ist keine allgemeine Textlösung, sondern eine Technik, um Debug-Text auf dem Bildschirm anzuzeigen
      Übrigens funktionieren die meisten Browser und Betriebssysteme, indem sie Text in Texturen zeichnen. Sie rendern Schrift dynamisch in Texturatlanten und erzeugen aus den Glyphen dieser Atlanten weitere Texturen für Teile des Anwendungsfensters. Wenn man sich im Browser Texturgrenzen anzeigen lässt, kann man alle Texturen sehen, und Rendering->Layer borders markiert jede Textur mit einem türkisfarbenen Rahmen
    • Auch heute ist es in der Regel gut, GPU-Speicher nicht übermäßig zu verschwenden. Große Textbox-Texturen müssen über den PCI-Bus übertragen werden, und je nach Upload-Zeitpunkt oder Verdrängung von GPU-Ressourcen kann es dabei zu Hängern kommen
      Wenn eine vergleichsweise langsame CPU viele unabhängige Textur-Textboxen rendern muss, summiert sich das schnell und zehrt am Budget
      Rendering mit Glyphenatlas ist hinsichtlich der Ressourcennutzung weiterhin deutlich besser. Moderne Text-Rendering-Pipelines verwenden beim Skalieren häufig SDFs oder kodierte Bézier-Kurven, um die Lesbarkeit von Glyphen zu verbessern, und auch das ist eine gute Methode, um noch sparsamer mit Speicher umzugehen
    • Ein einzelnes Rechteck zu zeichnen, das N Zeichen abdeckt, und im Shader die Glyphe auszuwählen, dürfte schneller sein, als für jedes Zeichen ein eigenes Rechteck zu zeichnen. Zumindest bei nichtproportionalen Schriften. Der tatsächliche Unterschied ist aber wahrscheinlich klein, weil die Zahl der Zeichen, die auf den Bildschirm passen, ohnehin begrenzt ist
      Aus Upload-Sicht hat man am Ende X Byte an Glyphen, die irgendwie in den GPU-Speicher gelangen müssen. Ob als Textur, Uniform-Daten oder Shader-Konstanten, macht für die Performance keinen großen Unterschied. Im Gegenteil: Wenn man sie wie im Original als Shader-Konstanten einbettet, kann der CPU-Aufwand sogar höher sein, weil der Shader-Compiler all diese Konstantendeklarationen verarbeiten muss
      Wichtig auf der GPU-Seite ist, welche Speicherebene beim Lesen der Glyphendaten genutzt wird. Texturzugriffe verwenden auf den meisten GPUs einen dedizierten L1-Cache, der vermutlich größer ist als der allgemeine L1-Cache. Auch die Datenreihenfolge spielt eine Rolle. Texturen werden gewöhnlich in einer Variante der Morton-Reihenfolge gespeichert, um Cache-Misses beim Shading von Pixelblöcken zu vermeiden. Für einen atlasbasierten Text-Renderer im Praxiseinsatz sind Texturen wahrscheinlich die bessere Wahl
      Korrektur: Ich habe die Frage falsch gelesen. Wenn es um den Vergleich zwischen dem Zeichnen einzelner Glyphen auf der GPU und dem Rendern eines gesamten Textblocks auf der CPU geht, dann ist das ein Trade-off zwischen Geschwindigkeit und Speicherplatz. Die Antwort hängt davon ab, wie viel Speicher man für Text verwenden will, ob sich der Text ändert und ob Effekte pro Zeichen nötig sind
    • Man kann die ganze Zeichenkette vor dem Upload rendern, aber dann verwendet man im Wesentlichen CPU-Rendering, und das ist langsamer, als wenn die GPU dieselbe Arbeit übernimmt
      Außerdem: Auch wenn man es „ohne Textur“ nennt, ist dieser Ansatz letztlich ebenfalls eine Textur. Die Textur ist nur in einem anderen Format und an einem anderen Ort gespeichert. Wirklich texturloses Font-Rendering würde Vektorkurven direkt zur Laufzeit auswerten
    • Das hängt von der Anwendung ab. Vor allem wenn man mit rechts-nach-links geschriebenen Zeichen, CJK oder Emojis rechnen muss, ist das der einfachste Weg. Die meisten Texte ändern sich nicht in jedem Frame, daher lohnt sich das Caching in Texturen, und für uns ist das gut genug
  • Es ist ziemlich verwirrend, dass erst gesagt wird, man werde kein Bitmap im Shader speichern, und dann genau erklärt wird, wie man ein Bitmap im Shader speichert
    Kurz gesagt: Eine Bitmap-Schriftart wurde im Shader eingebettet.

    • Nein, es ging darum, kein Bitmap in einer Textur zu speichern. Das ist etwas anderes, als es direkt in den Shader-Code einzubetten.
      Man kann es mit dem Unterschied vergleichen zwischen dem Speichern von Daten in einer separaten Datei, die zur Laufzeit eingelesen werden muss, und dem direkten Einfügen der Daten in den Quellcode.