Einfacher Ausbruch

von Christoph

Man sieht in der Software-Entwicklung immer wieder, dass die Leute eine technische Lösung einführen wollen und dann ihr Problem darauf anpassen (technology driven development). Wer kann es den Ingenieuren verdenken? Technologien machen Spaß, halten kreativ, schaffen Arbeit. Je komplexer desto mehr. So kommen Firmen dann zu Softwaresystemen, die am tatsächlichen Bedarf um ihrer selbst willen geschmeidig vorbei­laufen. Weil ja das Problem erst noch an die Lösung angepasst werden muss, dieser Schritt dann aber nicht mehr gegangen wird, falls er denn überhaupt möglich ist.

Warum erzähle ich das? Nun, ich habe das in meiner Freizeit natürlich auch so gemacht. Ich wollte mal wieder etwas mit Computergrafik, irgendwas mit Shadern entwickeln, also habe ich mir die Probleme SameGame, Ballout und Snake geschaffen.

Jetzt war es aber mal an der Zeit, dem Minimalismus zu huldigen. Nehmen wir an, wir haben die Spielidee und wählen davon ausgehend die Werkzeuge! In diesem Fall ist mein Problem ein Breakout-Klon und meine Lösung ohne prozedurale 3D-Geometrie, Beleuchtungsberechnung und Texturen. Einfach nur Rechtecke, Kreise und etwas Vektormathematik. Entwicklergolf sozusagen - wer die wenigsten Züge braucht, gewinnt.

Bildschirmfoto Breakout

Wenn man das so angeht, kommt schnell heraus, dass man die ganze Grafikkarten-Magie überhaupt nicht benötigt. Das ist auch irgendwie klar, es ging schon 1976 ohne. Man schreibt 2022 natürlich auch nicht mehr Assembler, aber man kann mal versuchen, sich auf die Bordmittel, in diesem Fall von MS Windows, zu beschränken. Es ist zugegebenermaßen etwas Augenwischerei: MS Windows ist, wenn man so will, die fetteste Abhängigkeit, die man sich überhaupt in ein Projekt holen kann. Für Spiele ist das meiner Ansicht nach okay und die Grundannahme ist hierbei, dass die zehntausenden Entwickler bei Microsoft ihre milliardenfach ausgerollte Komplexität einigermaßen im Griff haben. Hier also der Code-Umfang von Breakout in Direct2D für MS Windows.

D:\Projects\Games [master]> cloc.exe .\Breakout\
      35 text files.
      35 unique files.
      30 files ignored.

github.com/AlDanial/cloc v 1.90  T=0.10 s (235.4 files/s, 20617.1 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C++                              5            206             47            778
XML                              7              0              0            361
C/C++ Header                     8             65             23            204
Windows Resource File            1             51             63            163
JSON                             2              0              0             53
-------------------------------------------------------------------------------
SUM:                            23            322            133           1559
-------------------------------------------------------------------------------

Und schließlich die Downloads: Zip (32 Bit) | Zip (64 Bit). Wie immer gilt: einfach entpacken und ausprobieren, keine Installation, keine Systemveränderungen.

Marktperformance 2021

von Christoph

Dies ist die Fortsetzung meines Marktrückblicks von 2020. Es lief 2021 sehr gut. Beängstigend gut. So gut, dass statistisch gesehen die nächsten Jahre ziemlich mau ausfallen müssen damit der langfristige Mittelwert wieder passt. Ich habe lange überlegt, wie die Veränderung von 34% zu interpretieren ist. Wertsteigerung oder doch eher Kaufkraftverlust (Inflation)?

Da ich kein Volkswirt bin, muss ich mir hier mit einfacher Laienlogik helfen. Wertzuwachs hat für mich etwas von Leistung. Man hat eine besonders gute Strategie (Auswahl, Timing oder beides) und schlägt damit den Markt (dollargehandelter thesaurierender MSCI World dieses Jahr in € bei 32,6%). Inflation passiert einfach, egal was man macht. Bezüglich ersterem hatte ich ja letztes Jahr den tollen Plan, immer wieder nachzukaufen. Das war sicherlich richtig. Aber was man kauft, war im wesentlichen bedeutungslos. 2021 war es nur wichtig, breit investiert zu sein. War ich. Ergo handelte es sich um massive (Vermögenspreis-)Inflation.

Gewinn-Verlust-Chart-2021

Die Erkenntnis, dass Stock-Picking zu viel Aufwand für zu wenig Ertrag ist, reifte bei mir schon länger. Deswegen habe ich es dieses Jahr weitestgehend aufgegeben. Im Gegenteil, ähnlich wie bei Softwarekomplexität, glaube ich nun auch für mein Depot: einfacher ist besser. Werte, die zu lange unter-performed haben (und von denen ich nicht mehr überzeugt bin), habe ich deswegen in den MSCI World umgeschichtet. Einzelwerte, die gut liefen (zum Beispiel MSFT, NVDA, PG), habe ich natürlich laufen lassen. Ich bin jetzt aber auch nicht aktiver Rebalancer. Mein Plan für 2022 ist, den relativen Anteil der Einzelwerte durch Aufblähen der ETFs (via Zuflüsse) zu reduzieren. An den nun insgesamt 12 Positionen in meinem Depot will ich festhalten.

Ein paar Worte noch zu Anleihen. Ich habe das jetzt in 8 Jahren 4 mal probiert und bin nur einmal mit ganz wenig Gewinn da rausgegangen. Ok, die Verluste waren auch ganz wenig, aber nach meiner Erfahrung sind Anleihen keine sichere Kiste, die in jedes Depot gehören. Wenn eh Verluste nicht ausgeschlossen werden können, kann ich auch gleich defensive Aktien (Nestlé) oder ETFs nehmen. Also irgendwas habe ich hier nicht verstanden. Und ich geb's auch auf - Anleihen fasse ich nicht mehr an.

Die Ballspiel-Trilogie

von Christoph

»Wann hast du eigentlich zuletzt einen Arkustangens benutzt?«
»Na letzte Woche!«

Ja okay, ich gebe es zu: davor auch einige Jahre nicht mehr. Kürzlich habe ich es gebraucht, um den Winkel zwischen einem Fixpunkt und dem Mauszeiger zu bestimmen. Konkreter den Winkel eines am Ende fixierten virtuellen Kanonenrohrs und seiner beweglichen Mündung (gemessen zum Horizont).

Der Leser ahnt es schon. Ein neues Spiel! Und ich nenne es Ballout. Es ist im wesentlichen ein Klon von Puzzle Bobble (manchmal auch Bust a Move), ich habe es nur nicht so getauft, um nicht in irgendwelche markenrechtlichen Schusslinien zu geraten. Es basiert wieder einmal auf der von mir selbst entwickelten SameGame-Engine und enthält damit auch eine annehmbare physikalische Simulation.

Nach SameGame und Snake ist das nun schon das dritte Spiel auf dieser Technologie-Basis. Damit hat sich die Wiederverwendung wohl gelohnt - und natüüürlich war es von Anfang an als Trilogie geplant. Komplexitätsmäßig siedelt sich Ballout knapp über Snake an. Die Logik ist eben nicht übermäßig extravagant wenn man die Physik und Grafik bereits einmal vorbereitet hat.

Und jetzt nicht lange fackeln! Alle Spiele der Trilogie unter MS Windows ausprobieren: Zip (32 Bit) | Zip (64 Bit)

Snakegame Code-Metriken

von Christoph

Kürzlich habe ich irgendwo einen Artikel gesehen: "Game Engine unter 50 MByte Größe". Das war als Werbung gedacht und der herausragende Punkt war also offensichtlich wie schlank der Code ist. Ich habe mich gefragt, ob die das ernst meinen. Dann wurde mir klar, dass die Entwickler vermutlich den Begriff Engine falsch benutzen. Ich kann nur vermuten, dass in dem Paket auch Resourcen (Modelle, Texturen, Audio) und natürlich Dokumentation mit drin sind. Mich hätte aber schon interessiert, was die Größe nur der Algorithmen einer schlanken Game Engine ist.

Die Frage drängte sich umso mehr auf, weil ich ja vor Kurzem selbst wieder mit SameGame ein kleines Spiel geschrieben habe ‐ zugegeben ein sehr einfaches Spiel aber nichts desto trotz in modernem C++ mit GPU-Beschleunigung. Also nächster Schritt: den weitestgehend generischen Code in eine Bibliothek (SameGame Engine) ausgelagert und auf dieser Basis den Klassiker Snake nachgebaut. Am Ende sind alles nur Kugeln. Mittels cloc habe ich schließlich die Codezeilen der Bibliotheken und Anwendungen durchgezählt, ausgenommen Kommentare und Leerzeilen.

Etwa ein Drittel von SameGame war allgemeiner Grafik-Code (Geometrie, Instancing), dazu noch etwas Dateibehandlung (Konfiguration und Highscores). Nicht extrahiert habe ich Physik und sowieso nicht verschiebbar war die SameGame-Spiellogik. Neu hinzu kam ein signifikanter Anteil an Visual Studio Boilerplate, aber um den muss man sich glücklicherweise nicht übermäßig selbst sorgen. So ist im Resultat der ausgelagerte Grafik-Code in etwa genauso groß wie die Snake-Logik. Oder anders ausgedrückt: Snake nur halb so groß wie es ohne wiederverwendete "Engine" wäre.

Und hier noch die Downloads für MS Windows: Zip (32 Bit) | Zip (64 Bit)

Grafikperformance und Physik

von Christoph

Ich habe das neue SameGame noch mehr aufgebohrt. Nach der Fertigstellung von Gameplay und Grafik habe ich es knallhart auf Performance optimiert. Das ist bei so einem einfachen Spiel natürlich überhaupt nicht nötig (wir reden hier schließlich nicht von Doom Eternal), aber es geht ja um Spaß am Gerät. Insofern ist es also doch nötig.

Zunächst einmal habe ich Instancing eingeführt. Es werden letztlich immer nur Kugeln gezeichnet, also konstante Geometrie jeweils mit anderer Farbe und Koordinatentransformation. Der klassische Ansatz setzt Farbe, setzt Transformation und feuert Draw-Calls in einer Schleife, die auf der CPU (Hauptprozessor) läuft und regelmäßig an die GPU (Grafikkarte) übergibt. Instancing verschiebt das komplett auf die GPU: packe n Farben und n Transformationen in den Grafikspeicher und befehle der GPU genau einmal, die Kugelgeometrie n mal zu rendern.

Screenshot SameGame Physics

Das skaliert massiv. Ich habe das Spielfeld via Konfigurationsdatei testweise auf 150x150 vergrößert. Der CPU ist es egal, ob sie eine oder 20000 Transformationen in einem Rutsch auf die Grafikkarte lädt. Letztere wiederum rendert 16 Mio. Dreiecke (700 pro Kugel) schön verteilt auf Kernen und Pipelines inklusive Per-Pixel-Beleuchtung. Ab 20000 Kugeln fängt die Framerate langsam an zu sinken - aber alles noch spielbar. Man könnte das leicht noch weiter verbessern, indem man bei mehr Kugeln niedriger aufgelöste Geometrie verwendet (Level of Detail). Wenn irgendjemand jemals regelmäßig mit mehr als 20k Kugeln spielt, mache ich das vielleicht.

Nun langweilt sich die CPU also, abgesehen von ein paar Mauspositions-Berechungen. Wer den Screenshot oben aufmerksam betrachtet, wird feststellen, dass die Kugeln nun auch nicht mehr in einem Raster liegen. Das kommt von einer Arbeitsbeschaffungsmaßnahme. Es werkelt nun nämlich eine Physik-Engine, die dafür sorgt, dass die Kugeln realistisch fallen, voneinander und von den Begrenzungen abprallen sowie mit Spin und Drall rollen. Die Simulation läuft in einem separaten Thread, so dass Grafik-Updates davon nicht beeinträchtigt werden (abzüglich kurzer Mutex-Synchronisation). Unabhängig von der Grafikperformance aktualisiert die Physik die Spielwelt 60 mal pro Sekunde.

SameGame Physics (Download Zip für Windows, 64 Bit) benutzt also konstant zwei CPU-Kerne (1. Grafik/UI und 2. Physik) sowie alle Grafikkerne, die vorhanden sind. Schön, auf was man als Entwickler so alles zugreifen kann, um das Optimum herauszuholen.

Marktperformance 2020

von Christoph

Auf meinem Raspberry Pi läuft jeden Morgen um 6:30 ein Cron-Job, der den Gesamtwert meines Aktien- und ETF-Depots bestimmt und in eine SQLite-Datenbank schreibt. Nach einem Jahr gibt es hier also 365 Einträge. Das zu betrachten wäre allerdings einigermaßen langweilig weil ich regelmäßig Transaktionen vornehme und die Performance sozusagen von außen verzerrt wird.

In der Datenbank pflege ich aber auch die Transaktionen (Käufe und Verkäufe) und nun wird es interessant. Die kann man nämlich herausrechnen. Im Jahr 2020 waren es 51. Das klingt für einen Buy-and-Hold-Ansatz nach sehr viel. 12 davon waren Sparpläne, die kommen also so oder so. Der Rest wurde von meinem Crash-Plan ausgelöst. Als die Börsen eingebrochen sind, habe ich regelmäßig an bestimmten Schwellenwerten nachgekauft. Da die Schwellenwerte sehr zügig gerissen wurden, waren die zeitlichen Abstände des Nachkaufens auch kurz. Doch zurück zum Herausrechnen von Transaktionen. Wir nehmen den Depotwert vom 1.1.2020 als Basis (baseline) und:

  • für jeden Kauf wird ab Kaufdatum jeder Depotwert um den Kaufpreis reduziert
  • für jeden Verkauf wird ab Verkaufsdatum jeder Depotwert um den Verkaufspreis erhöht

Damit sehen wir nur noch die Performance des Depots vom 1. Januar und Veränderungen, die direkt aus dem Depotbestand generiert wurden. Kursbewegungen von Zukäufen (Δ) gehen ein, nicht jedoch die Zukäufe. Und hier ist das Ergebnis, die bereinigte Veränderung in Prozent:

Gewinn-Verlust-Chart

Für ein Krisenjahr doch garnicht so schlecht. Der Einbruch im März tat weh, aber er war gedämpft. Während es im Allgemeinen auch mal 40% abwärts ging, traf es mich "nur" mit 25%. Trotzdem kein schönes Gefühl, gerade ein Viertel ärmer zu sein als noch einen Monat zuvor. Im kumulierten Chart war das allerdings nicht so krass sichtbar. Ich hatte ja den Plan und die Reserven. Tatsächlich sah es so aus, als wäre ich nur kurz maximal 5% im Minus weil ich das Depot immer wieder hochgekauft habe. In der Gewinnzone war ich erstmals wieder im September, stabiler ab November. So werde ich das Jahr mit Kursgewinnen von 6% verlassen (zuzüglich ein paar Dividenden). Da wird so mancher hochbezahlter Fonds-Manager schlechter abschneiden. Klar, berauschend ist das auch nicht (in Krisen werden Reiche gemacht), allerdings bin ich ja eher defensiv aufgestellt. Das begrenzt die Verluste - aber eben auch die Gewinne.

Spieleentwicklung gestern und heute

von Christoph

Als Computergrafik-Gesamtübung habe ich SameGame noch einmal neu implementiert. Erst als ich fertig war, ist mir aufgefallen, dass das ziemlich genau 18 Jahre nach der ersten Version war. Das Programm ist jetzt also offiziell volljährig. Und tatsächlich habe ich den Original-Quelltext auch noch - oh man, wie sich die Zeiten ändern. Es folgt ein kurzer Vergleich zwischen damals (links im Bild) und heute (rechts im Bild).

Montage SameGame

SameGame32 schrieb ich 2002 im Borland C++-Builder. Visual Studio war noch ein teures Produkt für Profis (was habe ich mich gefreut, als Student an eine Vollversion zu kommen). Heute ist das umgekehrt: der C++-Builder wollte in die Oberschicht vordringen und hat dabei jegliches Klientel verloren. Visual Studio hat es als Community-Version in die Breite geschafft, der C++-Builder in die Bedeutungslosigkeit.

Die Fenstergröße war damals fixiert. Das klingt harmlos, war aber Symptom einer bedeutenden Einschränkung: das Spiel tat nur so, als wäre es 3D. Tatsächlich wurden Bitmaps mittels DirectX 7 an feste Koordinaten eines Rasters kopiert. Die Kugeln lagen als bmp-Datei in den Anwendungsresourcen und zwar alle 3 Farben aus jeweils 18 Winkeln (ingesamt 54 Standbilder). Eine volle Drehung bestand aus Abspielen der 18 Bilder. Aus den Mauskoordinaten ließ sich trivial bestimmen, welche Kugel gerade überfahren wird und damit die Animation abgespielt werden muss. Die Einzelbilder wurden mit POV-Ray vorberechnet. Ja, es gab bereits Raytracing, allerdings weit weg von Echtzeit.

Nun ist das komplett anders. Beleuchtungsberechnung in Hardware ist lange Standard. Dementsprechend werden keine Bitmaps mehr kopiert sondern triangulierte Kugeln im 3D-Raum gerendert. Mit der invertierten Projektion lässt sich bestimmen, welche Kugel selektiert ist und damit ist die Fenstergröße nun frei verstellbar. Ich habe mich an das Endresultat mit 3 Shadern herangetastet: Gitternetz-Shader, Textur-Shader und schließlich Phong-Beleuchtung, was sehr nahe am damaligen Effekt ist.

Meine Grafik-Hardware kratzt das überhaupt nicht. Sie liefert Bilder so schnell wie der Monitor sie darstellen kann. Die Animationen sind entsprechend weich. Was für ein Unterschied zu den 18 Einzelbildern. Der Refresh war seinerzeit übrigens an einen Timer gekoppelt, der auf 75 ms eingestellt war. Das ergibt 13 Frames pro Sekunde oder anderthalb Sekunden für eine komplette Kugeldrehung. Keine Ahnung, wie ich auf diese Zahl kam. Wahrscheinlich durch Ausprobieren bis es bei maximaler Resourcenschonung auf einem Röhrenmonitor ausreichend flüssig aussah.

Mehr Vulkan-Grafik

von Christoph

Wie zuletzt beschrieben, ist die Grafik-API Vulkan derzeit mein Hobbyschwerpunkt. Als weiteres Kleinprojekt habe ich den Quake 3 Model Viewer portiert. Das Ding begleitet mich nun schon sehr lange, von DirectX 8 über OpenGL 2 und 4 bis neuerdings Vulkan. Natürlich braucht man dafür keine derart schweren Geschütze - Quake 3 lief schon vor 20 Jahren rund. Allerdings ist das Format vergleichsweise leicht zu implementieren und man streift jedes Thema einmal: Texturierung, Animation, indizierte Koordinaten.

Screenshot Q3View Vulkan

Kühn wie ich so bin habe ich auch gleich einmal probiert, das Grafik-Backend (in diesem Fall natürlich Vulkan) austauschbar zu machen, um ggf. da einmal OpenGL oder sonstwas dranzuhängen. Zunächst für eine super Idee befunden, kann ich davon nun abraten. Die APIs sind derart unterschiedlich, dass wenig bleibt, was man abstrahieren kann. Das Ganze wirkt krass aufgesetzt und macht die Architektur nur komplizierter. Daher mein Ratschlag: wenn man sich für ein Grafik-API entscheidet, kann man ruhig all in gehen. Pluggable Backends können sich gern die großen Entwicklungsfirmen leisten, die Horden von Entwicklern an ihre Engines ansetzen.

Meine kleine Vulkan-Hilfsbibliothek ist weiter gewachsen, zum Beispiel um Klassen für Texturierung und Tiefen-Pufferung. Wie ich zuletzt schrieb: schade, dass jeder seine eigenen Wrapper erfindet, aber glücklicherweise schreibt man sie nur einmal. Es ist auch nicht so, dass ich nicht versucht habe, existierende Libraries zu finden. V-EZ (Vulkan easy) von AMD sah zum Beispiel vielversprechend aus. Der Solo-Entwickler darf daran allerdings nicht mehr entwickeln. Das Projekt ist also hochoffiziell tot, es steht nur nicht dran. Und von dieser Art findet man zahllose mehr. Die Grundsatzfrage bei der Sache ist natürlich: Was will man mit einem generischen Wrapper für Vulkan? Da kommt doch dann OpenGL raus. Die Frage ist berechtigt :)

Schließlich habe ich mir noch vcpkg für Bibliotheksmanagement angeschaut. Ich wollte ja das Dekomprimieren von Quake 3-Modellen nicht selber implementieren. Bisher habe ich zlib und weitere Bibliotheken immer für statischen CRT-Link gepatched und dann mit msbuild selbst gebaut. Stellt sich raus: das geht nun auch mit vcpkg. Die patchen Konfigurationen und bauen dann mit cmake. Klingt ganz einfach und praktisch, geht aber natürlich nicht ohne den üblichen Bloat. Die laden sich allen Ernstes beim ersten Ausführen eine portable Powershell, ein kleines MSys und Nuget runter. Gesamtwert entpackt: 1.2 GByte. Das ist bei heutiger Software leider voll der Trend und noch lange nicht das Ende der Fahnenstange. Ich habe mir kürzlich Skia angeschaut. Damit kann man 2D-Grafiken erstellen, also sowas wie eine Linie von oben links nach unten rechts zeichnen. Dazu braucht man ein Google Build-System, Python, Ninja und wenn man das Bauen dann auslöst, fängt das Script an, x abhängige Git-Repos zu kopieren (Unicode, Textrendering, whatever). Man braucht mehr als 2 GByte an Build-Werkzeugen (so groß wie meine erste Festplatte). Raus kommt eine DLL mit 7 Mbyte. Das ist doppelt so groß wie Turbo Pascal 7.

Ich werd zu alt für so'n Scheiß. Aber High Performance Computergrafik mit C++ ist schon ganz schön geil.