Snakegame Code-Metriken

Dienstag, 4. Mai 2021 14:00 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

Samstag, 2. Januar 2021 10:00 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.

Spieleentwicklung gestern und heute

Freitag, 16. Oktober 2020 14:30 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.