Wie lösen wir große Anforderungen an den Videospeicher in einem 2D-Spiel?


40

Wie lösen wir große Anforderungen an den Videospeicher in einem 2D-Spiel?


Wir entwickeln ein 2D-Spiel (Factorio) in Allegro C / C ++ und stehen vor dem Problem, dass die Anforderungen an den Videospeicher mit zunehmendem Spielinhalt steigen.

Wir sammeln derzeit alle Informationen zu Bildern, die zuerst verwendet werden sollen, beschneiden alle diese Bilder so weit wie möglich und ordnen sie so eng wie möglich in großen Atlanten an. Diese Atlanten werden im Videospeicher gespeichert, dessen Größe von den Systembeschränkungen abhängt. Derzeit sind es in der Regel 2 Bilder mit einer Größe von bis zu 8192 x 8192, sodass 256 MB bis 512 MB Videospeicher erforderlich sind.

Dieses System funktioniert ziemlich gut für uns, da wir mit einigen benutzerdefinierten Optimierungen und der Aufteilung des Render- und Update-Threads in der Lage sind, Zehntausende von Bildern auf dem Bildschirm mit 60 fps zu zeichnen. Wir haben viele Objekte auf dem Bildschirm, und das Ermöglichen einer großen Verkleinerung ist eine wichtige Voraussetzung. Da wir noch mehr hinzufügen möchten, wird es einige Probleme mit den Anforderungen an den Videospeicher geben, so dass dieses System möglicherweise nicht aushält.

Eines der Dinge, die wir versuchen wollten, ist ein Atlas mit den häufigsten Bildern und der zweite als Cache. Die Bilder würden bei Bedarf aus der Speicher-Bitmap dorthin verschoben. Bei diesem Ansatz gibt es zwei Probleme:

  1. Das Zeichnen von Speicher-Bitmap zu Video-Bitmap ist in Allegro schmerzhaft langsam.
  2. Es ist nicht möglich, mit einer anderen Video-Bitmap als dem Haupt-Thread in Allegro zu arbeiten, daher ist dies praktisch unbrauchbar.

Hier sind einige zusätzliche Anforderungen, die wir haben:

  • Das Spiel muss deterministisch sein, damit die Leistungsprobleme / Ladezeiten den Spielstatus niemals ändern können.
  • Das Spiel ist in Echtzeit und bald auch im Mehrspielermodus. Wir müssen um jeden Preis das kleinste Ruckeln vermeiden.
  • Der Großteil des Spiels ist eine durchgehende offene Welt.

Der Test bestand aus dem Zeichnen von 10 000 Sprites in einem Stapel für Größen von 1x1 bis 300x300, mehrmals für jede Konfiguration. Ich habe die Tests mit der Nvidia Geforce GTX 760 durchgeführt.

  • Das Zeichnen von Video-Bitmaps zu Video-Bitmaps nahm 0,1us pro Sprite in Anspruch, wenn sich die Quell-Bitmap nicht zwischen einzelnen Bitmaps änderte (die Atlas-Variante). Die Größe spielte keine Rolle
  • Das Zeichnen von Video-Bitmaps in Video-Bitmaps nahm, während die Quell-Bitmaps zwischen Zeichnungen umgeschaltet wurden (Nicht-Atlas-Variante), 0,56 us pro Sprite in Anspruch. Auch die Größe spielte keine Rolle.
  • Das Zeichnen von Speicherbitmaps auf Videobitmaps war wirklich verdächtig. Größen von 1x1 bis 200x200 brauchten 0,3us pro Bitmap, also nicht so schrecklich langsam. Bei größeren Formaten begann sich die Zeit sehr dramatisch zu erhöhen, und zwar bei 9us für 201x201 auf 3116us für 291x291.

Durch die Verwendung von Atlas wird die Leistung um einen Faktor größer als 5 erhöht. Wenn ich 10 ms für das Rendern hatte, bin ich mit einem Atlas auf 100.000 Sprites pro Frame und ohne diesen auf 20.000 Sprites beschränkt. Das wäre problematisch.

Ich habe auch versucht, eine Möglichkeit zum Testen der Bitmap-Komprimierung und des 1bpp-Bitmap-Formats für Schatten zu finden, aber ich konnte in Allegro keine Möglichkeit finden, dies zu tun.


1
Großer Fan Ihres Spiels, ich habe die Indiegogo-Kampagne unterstützt. Ich mache alle paar Monate Spaß daran. Gute Arbeit bisher! Ich habe die "Welche Technologie soll verwendet werden?" - Fragen entfernt, die für die Site nicht relevant sind. Die verbleibenden Fragen sind noch ziemlich weit gefasst. Wenn Sie etwas Spezifischeres haben, sollten Sie versuchen, den Umfang einzugrenzen.
MichaelHouse

Danke für die Unterstützung. Wo kann man sich also die Frage stellen, welche Technologie dann verwendet werden soll? Ich bin nicht auf der Suche nach einer Antwort mit einer bestimmten Motorempfehlung, aber ich war nicht in der Lage, einen detaillierten Vergleich von 2D-Motoren zu finden und sie einzeln manuell zu überprüfen, einschließlich Leistungs- und Benutzerfreundlichkeitstests, würde Ewigkeiten in Anspruch nehmen.
Marwin

Unten auf dieser Seite finden Sie einige Stellen, an denen Sie Fragen stellen können, z. B. "Welche Technologie verwenden?". Sie haben eine absolut gültige und vernünftige Frage, es ist einfach nicht die Art von Frage, mit der wir uns auf dieser Site befassen. Auch wenn Sie nicht nach einem bestimmten Motor suchen, ist dies wirklich die einzige Möglichkeit, die Frage zu beantworten: "Gibt es eine Technologie, die X unterstützt?". Jemand könnte nur mit "Ja" antworten und keine Empfehlung für eine bestimmte geben, aber das wäre nicht sehr hilfreich. Viel Glück damit!
MichaelHouse

2
Komprimieren Sie Ihre Texturen?
GuyRT

3
@Marwin, Komprimierte Texturen erzielen möglicherweise eine bessere Leistung als unkomprimierte Texturen, da sie die benötigte Speicherbandbreite reduzieren (dies gilt insbesondere für mobile Plattformen, auf denen die Bandbreite viel geringer ist). Sie können viel Speicherplatz sparen, indem Sie Ihre Texturen komprimieren. Wirklich, der einzige Nachteil sind die Artefakte, die unvermeidlich eingeführt werden.
GuyRT

Antworten:


17

Wir haben einen ähnlichen Fall mit unserem RTS (KaM Remake). Alle Einheiten und Häuser sind Sprites. Wir haben 18.000 Sprites für Einheiten und Häuser und Gelände sowie weitere ca. 6.000 für Teamfarben (angewendet als Masken). Lang gestreckt haben wir auch ungefähr 30 000 Zeichen, die in Schriften verwendet werden.

Daher gibt es einige Optimierungen für die von Ihnen verwendeten RGBA32-Atlanten:

  • Teilen Sie Ihren Sprites-Pool zuerst in viele kleinere Atlanten auf und verwenden Sie sie nach Bedarf, wie in anderen Antworten beschrieben. Dies ermöglicht es auch, unterschiedliche Optimierungstechniken für jeden Atlas einzeln anzuwenden . Ich vermute, Sie werden ein bisschen weniger verschwendeten Arbeitsspeicher haben, weil beim Packen in so große Texturen normalerweise unbenutzte Bereiche unten sind.

  • Versuchen Sie es mit palettierten Texturen . Wenn Sie Shader verwenden, können Sie die Palette im Shader-Code "anwenden".

  • Sie könnten eine Option hinzufügen, um RGB5_A1 anstelle von RGBA8 zu verwenden (wenn zum Beispiel Schachbrettschatten für Ihr Spiel in Ordnung sind). Vermeiden Sie nach Möglichkeit 8-Bit-Alpha und verwenden Sie RGB5_A1 oder gleichwertige Formate mit geringerer Genauigkeit (wie RGBA4). Sie beanspruchen die Hälfte des Platzes.

  • Stellen Sie sicher, dass Sie Sprites eng in Atlanten packen (siehe Algorithmen zum Packen von Behältern), drehen Sie Sprites bei Bedarf und prüfen Sie, ob Sie transparente Ecken für Rhombus-Sprites überlappen können.

  • Sie können versuchen, Hardware-Komprimierungsformate (DXT, S3TC usw.) zu verwenden - sie können die RAM-Nutzung drastisch reduzieren, aber auf Komprimierungsartefakte prüfen - bei einigen Bildern kann der Unterschied unbemerkt bleiben (Sie können dies selektiv verwenden, wie im ersten Aufzählungspunkt beschrieben). aber auf einigen - sehr aussprechen. Unterschiedliche Komprimierungsformate verursachen unterschiedliche Artefakte. Wählen Sie daher möglicherweise eines aus, das für Ihren Kunststil am besten geeignet ist.

  • Sehen Sie sich an , wie Sie große Sprites (natürlich nicht manuell, sondern in Ihrem Texturatlas-Packer) in statische Hintergrund-Sprites und kleinere Sprites für animierte Teile aufteilen.


2
+1 für die Verwendung von DXT, ist es eine sehr gute Sache zu haben. Hervorragende Komprimierung und direkte Verwendung durch die GPU, sodass der Overhead minimal ist.

1
Ich bin mit dxt einverstanden. Sie könnten auch nach DXT7-Unterstützung (DX11 + Hardware) fragen, die dieselbe Größe wie DXT1 hat, aber (anscheinend) eine höhere Qualität aufweist. Sie müssten jedoch entweder die doppelten Texturen (eine DXT7 und eine DXT1) haben oder während des Ladens komprimieren / dekomprimieren.
Programmdude

5

Zunächst müssen Sie mehr, kleinere Texturatlanten verwenden. Je weniger Texturen Sie haben, desto schwieriger und starrer wird die Speicherverwaltung. Ich würde eine Atlasgröße von 1024 vorschlagen, in welchem ​​Fall Sie 128 Texturen anstelle von 2 haben würden, oder 2048, in welchem ​​Fall Sie 32 Texturen haben würden, die Sie nach Bedarf laden und entladen könnten.

Die meisten Spiele führen diese Ressourcenverwaltung durch, indem sie Ebenengrenzen haben, während auf einem Ladebildschirm alle Ressourcen, die im nächsten Level nicht mehr benötigt werden, entladen und die benötigten Ressourcen geladen werden.

Eine weitere Option ist das Laden auf Abruf, das erforderlich wird, wenn Ebenengrenzen unerwünscht sind oder sogar eine einzelne Ebene zu groß ist, um in den Speicher zu passen. In diesem Fall versucht das Spiel vorherzusagen, was der Spieler in Zukunft sehen wird, und lädt dies im Hintergrund. (Zum Beispiel: Dinge, die derzeit 2 Bildschirme vom Player entfernt sind.) Gleichzeitig werden Dinge entladen, die längere Zeit nicht mehr verwendet wurden.

Es gibt jedoch ein Problem: Was passiert, wenn etwas Unerwartetes passiert ist, das das Spiel nicht vorhersehen konnte?

  • Panik und Anzeige eines Ladebildschirms, bis alle notwendigen Dinge geladen sind. Dies könnte sich störend auf die Erfahrung auswirken.
  • Haben Sie Sprites mit niedriger Auflösung für alles vorinstallierte, setzen Sie das Spiel fort und ersetzen Sie sie, sobald das Laden der Sprites mit hoher Auflösung abgeschlossen ist. Dies könnte für den Spieler billig aussehen.
  • Beeinflusst das Gameplay und verzögert das Event so lange wie nötig. ZB laichen Sie diesen Feind nicht, bis seine Grafiken geladen sind. Öffne diese Schatzkiste nicht, bevor nicht alle Grafiken für diese Beute geladen sind.

Ich habe einige der Anforderungen hinzugefügt, die ich weggelassen habe. Der Ladebildschirm oder irgendeine Art des Ladens ist nicht möglich. Alles muss im Hintergrund oder zwischen einzelnen Ticks (jeweils weniger als 15 ms) durchgeführt werden, während die meiste Zeit normalerweise bereits für Render-Vorbereitungen und Spiele-Updates verwendet wird. Wie auch immer, das Teilen in kleinere Teile kann zu einer gewissen Flexibilität beim Umschalten führen. Dies ist mit Sicherheit schneller. Die Frage ist, wie sehr dies die Leistung beim Rendern beeinträchtigt, da das Umschalten der Quell-Bitmap beim Zeichnen das Rendern verlangsamt. Ich müsste genau messen, um zu sagen, wie viel.
Marwin

@ Marwin Leistungseinbußen, ja, aber da Sie sich mit 2D beschäftigen, sollte es immer noch weit davon entfernt sein, dass es zu einem Problem wird. Wenn das Rendern derzeit 1 ms pro Frame dauert und bei Verwendung kleinerer Texturen plötzlich 2 ms erforderlich sind, ist dies immer noch mehr als schnell genug, um konsistente 60 FPS zu erreichen. (16 ms)
API-Beast

@ Marwin Multiplayer ist knifflig, immer war, wird immer sein. Sie werden dort höchstwahrscheinlich Kompromisse eingehen müssen. Sie werden stottern, einfach weil Sie Daten über das Internet übertragen müssen, Pakete verloren gehen, Pings plötzlich ansteigen können usw. Stottern ist unvermeidlich, was wichtiger ist, ist, das Netzwerkmodell selbst stottern zu können. Wissen, wann man wartet und wie man auf andere Spieler wartet.
API-Beast

Hallo, Stottern ist im Mehrspielermodus fast vermeidbar. Wir arbeiten gerade an diesem Bereich, und ich glaube, wir haben einen guten Plan. Ich könnte sogar meine eigene Frage posten und beantworten, die beschreibt, was wir später im Detail recherchiert haben :) Es mag überraschen, aber die Renderzeit ist tatsächlich ein Problem. Wir haben viele Optimierungen vorgenommen, um das Rendern zu beschleunigen. Das Hauptrendern wird jetzt in separaten Threads und anderen kleinen Änderungen erstellt. Vergessen Sie nicht, dass der Spieler bei maximalem Zoom problemlos Zehntausende von Sprites gleichzeitig sehen kann. Und wir möchten später sogar noch höhere Zoomstufen zulassen.
Marwin

@Marwin Hm, 10k-Objekte sollten normalerweise für einen PC oder ein modernes Laptop kein Problem sein. Wenn Sie eine ordnungsgemäße Stapelverarbeitung verwenden, haben Sie Ihren Rendering-Code profiliert?
API-Beast

2

Wow, das ist eine Menge Animations-Sprites, die vermutlich aus 3D-Modellen generiert wurden.

Du solltest dieses Spiel wirklich nicht in rohem 2D machen. Wenn Sie die Perspektive festgelegt haben, passiert etwas Lustiges. Sie können vorgerenderte Sprites und Hintergründe nahtlos mit live gerenderten 3D-Modellen mischen, die von einigen Spielen häufig verwendet wurden. Wenn Sie so feine Animationen wünschen, scheint dies die natürlichste Methode zu sein. Holen Sie sich eine 3D-Engine, konfigurieren Sie sie für die Verwendung der isometrischen Perspektive und rendern Sie die Objekte, für die Sie weiterhin Sprites verwenden, als einfache flache Oberflächen mit einem Bild darauf. Und Sie können die Texturkomprimierung mit einer 3D-Engine verwenden, das allein ist ein großer Fortschritt.

Ich denke nicht, dass das Laden und Entladen viel für Sie bedeutet, da Sie so ziemlich alles gleichzeitig auf dem Bildschirm haben können.


2

Suchen Sie zunächst das effizienteste Texturformat, mit dem Sie zufrieden sind, unabhängig davon, ob es sich um RGBA4444 oder DXT-Komprimierung usw. handelt. Wenn Sie mit den in einem DXT-Alpha-komprimierten Bild erzeugten Artefakten nicht zufrieden sind, ist dies möglich Um die Bilder mithilfe der DXT1-Komprimierung für die Farbe in Kombination mit einer 4 oder 8-Bit-Graustufen-Maskentextur für das Alpha nicht transparent zu machen? Ich stelle mir vor, Sie würden für die GUI auf RGBA8888 bleiben.

Ich befürworte die Aufteilung in kleinere Texturen mit dem von Ihnen gewählten Format. Bestimmen Sie die Elemente, die immer auf dem Bildschirm angezeigt und daher immer geladen werden. Dies können Gelände- und GUI-Atlanten sein. Ich würde dann die verbleibenden Elemente, die üblicherweise zusammen gerendert werden, so weit wie möglich aufteilen. Ich kann mir nicht vorstellen, dass Sie zu viel Leistung verlieren, selbst wenn Sie bis zu 50-100 Draw Calls auf dem PC ausführen, aber korrigieren Sie mich, wenn ich falsch liege.

Der nächste Schritt besteht darin, die Mipmap-Versionen dieser Texturen zu generieren, wie oben ausgeführt. Ich würde sie nicht in einer einzelnen Datei sondern separat speichern. Sie würden also 1024x1024, 512x512, 256x256 usw. Versionen jeder Datei erhalten, und ich würde dies tun, bis ich die niedrigste Detailstufe erreiche, die ich jemals angezeigt haben möchte.

Nachdem Sie nun über die separaten Texturen verfügen, können Sie ein LOD-System (Level of Detail) erstellen, das Texturen für die aktuelle Zoomstufe lädt und Texturen entlädt, wenn sie nicht verwendet werden. Eine Textur wird nicht verwendet, wenn das Element, das gerendert wird, nicht auf dem Bildschirm angezeigt wird oder für die aktuelle Zoomstufe nicht erforderlich ist. Versuchen Sie, die Texturen in einem von den Aktualisierungs- / Renderthreads getrennten Thread in den Video-RAM zu laden. Sie können die niedrigste LOD-Textur anzeigen, bis die gewünschte geladen ist. Dies kann manchmal zu einem sichtbaren Wechsel zwischen einer Textur mit niedrigen und hohen Details führen. Ich stelle mir jedoch vor, dass dies nur dann der Fall ist, wenn Sie beim Bewegen über die Karte extrem schnell zoomen. Sie könnten das System intelligent machen, indem Sie versuchen, vorab zu laden, wo sich die Person Ihrer Meinung nach bewegen oder zoomen wird, und so viel wie möglich innerhalb der aktuellen Speicherbeschränkungen laden.

So etwas würde ich testen, um zu sehen, ob es hilft. Ich stelle mir vor, dass Sie für extreme Zoomstufen zwangsläufig ein LOD-System benötigen.


1

Ich glaube, der beste Ansatz ist es, die Textur in viele Dateien zu teilen und sie bei Bedarf zu laden. Wahrscheinlich besteht Ihr Problem darin, dass Sie versuchen, größere Texturen zu laden, die Sie für eine vollständige 3D-Szene benötigen, und Sie verwenden hierfür Allegro.

Für die große Verkleinerung, die Sie anwenden möchten, müssen Sie Mipmaps verwenden. Mipmaps sind Versionen Ihrer Texturen mit niedrigerer Auflösung, die verwendet werden, wenn die Objekte weit genug von der Kamera entfernt sind. Dies bedeutet, dass Sie Ihre 8192x8192 als 4096x4096 und dann eine andere mit 2048x2048 usw. speichern können. Je kleiner Sie das Sprite auf dem Bildschirm sehen, desto niedriger sind die Auflösungen. Sie können beide als separate Texturen speichern oder beim Laden die Größe ändern (das Generieren von Mipmaps zur Laufzeit verlängert jedoch die Ladezeiten für Ihr Spiel).

Ein ordnungsgemäßes Verwaltungssystem würde die erforderlichen Dateien bei Bedarf laden und die Ressourcen freigeben, wenn sie von niemandem verwendet werden, sowie andere Dinge. Ressourcenverwaltung ist ein wichtiges Thema in der Spieleentwicklung, und Sie beschränken Ihre Verwaltung auf eine einfache Koordinatenzuordnung auf eine einzige Textur, die so gut wie gar keine Verwaltung hat.


1
Mit Teilen in Dateien meinen Sie Dateien auf der Festplatte? Ich gehe davon aus, dass ich alle Bilder für den Anfang im RAM speichern könnte und sogar das Kopieren von der Speicher-Bitmap zur Video-Bitmap derzeit zu langsam ist, sodass das Laden von der Festplatte sicherlich noch langsamer wäre. Mimpaps zu haben hilft mir nicht, da ich immer noch die größte auflösung in vram haben werde.
Marwin

Ja, Sie müssen nicht alles laden, Sie müssen nur das laden, was Sie verwenden. Wenn Sie ein Pixel in einer im VRAM geladenen Textur ändern möchten, muss das System die GESAMTE TEXTUR in den RAM verschieben, damit Sie ein einzelnes Pixel ändern und es wieder in den VRAM verschieben können. Wenn Sie alles in einer einzigen Textur haben, müssen Sie 256 MB in den RAM und dann wieder in den VRAM verschieben, wodurch der gesamte Computer gesperrt wird. Die richtige Vorgehensweise besteht darin, es in verschiedene Dateien und Texturen aufzuteilen.
Pablo Ariel

Die Änderung der Textur, die das Kopieren in den Speicher und zurück in den RAM auslöst, gilt nur für permanente Bitmaps. Der Cache würde wahrscheinlich nicht auf permanent gesetzt. Der einzige Nachteil wäre die Notwendigkeit, ihn zu aktualisieren, wenn die Anzeige verloren geht / gefunden wird. Aber in der Allegro dauert sogar das einfache Kopieren von 640x480 Bildern von VRAM zu Speicher-Bitmap (Spielvorschau speichern) ziemlich lange.
Marwin

1
Ich muss alles in einer großen Textur haben, um die Zeichnung selbst zu optimieren, ohne dass der Effekt des Wechsels des Kontexts zwischen einzelnen Sprites das Rendern zumindest in Allegro zu sehr verlangsamt. Verstehen Sie mich nicht falsch, aber Sie sind offensichtlich eine Art Kapitän, da Sie mir vage vorschlagen, etwas zu tun, was ich in dieser Frage verlange.
Marwin

1
Wenn ich diese mip-mapped Texturen in verschiedenen Dateien hätte, müsste ich den gesamten Atlas neu laden, wenn der Player hineinzoomt. Da die Engine höchstens nur wenige Einheiten von ms hat, sehe ich keinen Weg, wie das geht.
Marwin

0

Ich empfehle, mehr Atlas-Dateien zu erstellen, die mit zlib komprimiert und aus der Komprimierung für jeden Atlas gestreamt werden können. Wenn Sie mehr Atlas-Dateien und kleinere Dateien haben, können Sie die Menge der aktiven Bilddaten im Videospeicher begrenzen. Implementieren Sie außerdem den Dreifachpuffermechanismus, sodass Sie jeden Zeichnungsrahmen früher vorbereiten und möglicherweise schneller fertiggestellt werden, sodass die Ruckler nicht auf dem Bildschirm angezeigt werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.