Theoretische und technische Analyse der CPU-Engpässe in der Godot 4.7 Render-Architektur
Godot

Theoretische und technische Analyse der CPU-Engpässe in der Godot 4.7 Render-Architektur

AutorGameDev Base
Datum28. Oktober 2026
21 Min. Lesezeit

Godot 4.7 Render-Architektur: Theoretische und technische Analyse von CPU-Engpässen

Allgemeiner architektonischer Rahmen und CPU-GPU-Synchronisationspunkte

Die moderne Render-Architektur der Godot-Engine, insbesondere in Versionen, die 3D- und fortgeschrittene 2D-Grafiken unterstützen, basiert auf einer komplexen Schichtenstruktur, um Plattformunabhängigkeit und den Zugriff auf Low-Level-Grafik-APIs (Vulkan, Direct3D 12, Metal) zu gewährleisten. Der Grundstein dieser Architektur ist eine Abstraktionsschicht namens RenderingDevice, die von allen modernen Renderern verwendet wird. RenderingDevice zielt darauf ab, die APIs verschiedener Grafik-Backends in einer gemeinsamen Schnittstelle zu vereinen, wodurch die Kompilierung für mehrere Plattformen mit derselben Codebasis ermöglicht wird. Diese Schicht verwaltet grundlegende GPU-Ressourcen und -Vorgänge wie Befehlslisten (draw_list, compute_list, raytracing_list), die eine effiziente Übertragung hochrangiger visueller Assets (Meshes, Materialien, Lichter), die vom RenderingServer verwaltet werden, an die GPU ermöglichen. Während dieser architektonische Ansatz darauf abzielt, Code-Duplizierung zu reduzieren und die Integration neuer Backends zu erleichtern, bringt er gleichzeitig eine erhebliche CPU-Last mit sich, die hinter jedem API-Aufruf verborgen ist. Jeder Renderbefehl, Shader-Wechsel oder Zustandswechsel erfordert eine beträchtliche Menge an CPU-Arbeit, damit die GPU die aktuelle Konfiguration validieren kann; dies macht es zwingend erforderlich, teure API-Aufrufe zu minimieren und "Batching"-Techniken zu verwenden, um ähnliche Objekte in einem einzigen Vorgang zu rendern, was das Hauptziel von Render-Optimierungen darstellt.

Die Synchronisation zwischen CPU und GPU ist für den reibungslosen Ablauf des Renderprozesses von entscheidender Bedeutung, stellt jedoch gleichzeitig eine große potenzielle Quelle für CPU-Engpässe dar. Godot unterstützt sowohl synchrone als auch asynchrone Synchronisationsmechanismen. Die Klasse RenderingDevice bietet explizite Synchronisationspunkte, die die CPU warten lassen. Beispielsweise blockiert die Methode sync() den aufrufenden Thread, ohne darauf zu warten, dass die GPU alle Aufgaben abschließt, was aus Leistungssicht kostspielig ist. In ähnlicher Weise kann die Methode force_sync() ebenfalls einen erzwungenen CPU-GPU-Synchronisationspunkt erzeugen und zu Leistungseinbußen führen. Asynchrone Vorgänge hingegen, wie Funktionen wie texture_get_data_async, die zum Lesen einer Textur von der GPU verwendet werden, verhindern, dass die CPU 525 7 533 1619 533 21

Leerlaufzeit verbringt, während sie darauf wartet, dass die GPU fertig wird. Solche Vorgänge haben jedoch eine Verzögerung (Frame Delay), und diese Verzögerung kann CPU-Zeit in einem Ausmaß verbrauchen, das durch die Projekteinstellung rendering/ rendering_device/vsync/frame_queue_size bestimmt wird. Darüber hinaus kann jeder Aufruf von texture_get_data_async etwa 4 ms CPU-Zeit in Anspruch nehmen; das gleichzeitige Starten einer großen Anzahl von Anforderungen kann die gesamte CPU-Last erhöhen und zu einem gegenteiligen Leistungsabfall führen. Dies zeigt, wie wichtig es ist, bei der asynchronen Programmierung ein sorgfältiges Gleichgewicht zu finden.

Eine der bemerkenswertesten Erkenntnisse ist, dass die im Vulkan-Backend vorhandene Synchronisationslogik einen grundlegenden Fehler aufweist. In den angegebenen Quellen wurde aufgezeigt, dass die Funktion RenderingDeviceVulkan::swap_buffers nicht die korrekte Reihenfolge einhält, d. h. sie wartet nicht darauf, dass die GPU die Ressourcen des vorherigen Frames freigibt, während ein neuer Frame vorbereitet wird, was zu potenziellen "Use-after-free"-Fehlern und schwerwiegenden Abstürzen wie DEVICE_LOST führen kann. Dieser Fehler ist nicht nur ein Leistungsproblem, sondern zeichnet sich als architektonischer Mangel aus, der die Stabilität und Zuverlässigkeit der Engine gefährden kann. Zusammenfassend lässt sich sagen, dass die RenderingDevice-Schicht zwar Plattformunabhängigkeit bietet, jeder Befehl, der sie durchläuft, jedoch eine CPU-Last darstellt und die Verwendung von asynchronen APIs sowie Fehler in der Synchronisationslogik ein sensibles Gleichgewichtsspiel sind, das die CPU-Leistung bestimmt.

Synchronisationsmethode / -punkt

Beschreibung

Potenzieller CPU-Engpass

RenderingDevice.submit() (für lokale Geräte)

Sendet Befehlslisten von Worker-Threads, die außerhalb des primären Render-Threads laufen, an die GPU.

Kosten für Thread-Synchronisation und Befehlsübermittlung.

RenderingDevice.sync() (für lokale Geräte)

Wartet auf den Abschluss von Worker-Threads, die außerhalb des primären Render-Threads laufen.

CPU blockiert, während sie auf die GPU wartet, was zu CPU-Zeitverlust führt.

RenderingDevice.force_sync()

Löst einen erzwungenen CPU-GPU-Synchronisationspunkt aus.

CPU blockiert (Stalling), während sie auf die aktuelle Arbeit der GPU wartet. Sehr kostspielig.

RenderingDevice.texture_get_data_async()

Ruft die Daten einer Textur auf der GPU asynchron zur CPU ab.

Verzögerung, CPU ist inaktiv, während sie darauf wartet, dass die GPU die Daten bereitstellt.

RenderingDevice.buffer_get_data_async()

Ruft die Daten eines Buffers auf der GPU asynchron zur CPU ab.

Wartezeit der CPU, während sie auf den Erhalt der Daten wartet.

Vulkan-Backend-Synchronisationsfehler

swap_buffers kann einen neuen Frame starten, ohne in der richtigen Reihenfolge zu warten.

Risiko von "Use-after-free" und DEVICE_LOST durch den Versuch, Ressourcen freizugeben, die die GPU noch nicht fertig bearbeitet hat.

Eine weitere wichtige Schicht in der Architektur ist der RenderingServer. Dieser Server arbeitet unabhängig vom Node-basierten Szenenbaum und wird für die Erstellung und Verwaltung aller visuellen Inhalte verwendet. Die API des RenderingServer basiert auf eindeutigen Identifikatoren, den sogenannten RID (Resource ID) 32 33 32 4 533 21 32 33 5 4 821

. Diese IDs sind opake Zeiger anstelle von tatsächlichen Speicherreferenzen und ermöglichen das Erstellen, Aktualisieren und Löschen von Ressourcen. Dieses Design ermöglicht es Entwicklern, die den RenderingServer direkt unter Umgehung des Szenenbaums nutzen, mit einem effizienteren System zu arbeiten, das frei von den Kosten des Baum-Systems ist (Node-Lebenszyklus-Benachrichtigungen, Transform-Propagierung usw.). Beispielsweise ist das Aktualisieren der Transformation eines RID-basierten Objekts mit instance_set_transform() weitaus schneller als das Durchführen desselben Vorgangs mit Node3D.set_transform(). Diese Trennung bietet ein kritisches Werkzeug für Anwendungen, die die CPU-Leistung maximieren möchten.

Der RenderingServer ermöglicht zudem durch Mechanismen wie call_on_render_thread(Callable) den sicheren Aufruf aus dem Hauptanwendungs-Thread. Dies ermöglicht es dem Haupt-Thread, Aufgaben an einen für Render-Vorgänge reservierten Thread zu senden, aber dieser Sendevorgang ist ebenfalls ein Synchronisationspunkt und verbraucht CPU-Zeit. Daher sollte man nicht vergessen, dass der RenderingServer zwar einen effizienteren Weg bietet, die Mechanismen, die zu seiner Nutzung erforderlich sind, jedoch selbst CPU-Kosten verursachen. CPU-Zeit-Messungen können mit Funktionen wie get_frame_setup_time_cpu() durchgeführt werden, die die gesamte für die Vorbereitung eines Frames aufgewendete CPU-Zeit angeben, was ein wertvoller Indikator für das Verständnis der Gesamtauswirkung der Vorgänge innerhalb des RenderingServer ist. Zusammenfassend lässt sich sagen, dass die Render-Architektur von Godot, so sehr sie auch Plattformunabhängigkeit und Flexibilität bietet, eine komplexe Struktur ist, die bei der CPU-GPU-Synchronisation Präzision erfordert, potenzielle Fehler aufweist und korrekte Optimierungsstrategien verlangt.

CPU-Engpässe und Optimierungs-Trade-offs im 2D-Renderprozess

Der 2D-Renderprozess in Godot weist im Vergleich zum 3D-Rendering andere Leistungsdynamiken auf, da er normalerweise mit einfacheren Geometriesätzen und einer geringeren Anzahl an Materialien arbeitet. 2D-Spiele neigen dazu, CPU-seitig zum Flaschenhals zu werden, insbesondere bei Top-Down- oder Side-Scroller-Projekten, bei denen Tausende von Sprites gleichzeitig auf dem Bildschirm zu sehen sind. Dies zeigt, dass der grundlegendste CPU-Engpass bei 2D-Renderprozessen darin besteht, einzelne GPU Draw Calls für Hunderte oder Tausende von separaten Objekten zu senden. Jeder Draw Call erfordert eine erhebliche Menge an CPU-Arbeit, um den aktuellen Zustand der GPU (aktiver Shader, Material, Vertex-Format usw.) zu validieren, wenn er über die API-Schnittstelle (OpenGL, Vulkan usw.) geleitet wird. Diese Validierungsvorgänge werden als Hintergrund-Hosting hinter der API bezeichnet, und diese Kosten steigen exponentiell mit dem pro Objekt durchgeführten Zustandswechsel (State Switching). Daher ist das Hauptziel von 2D-Render-Optimierungen, diese teuren 721 22 22 8 8 11 1619 19

API-Aufrufe zu minimieren und "Batching"-Techniken zu verwenden, um ähnliche Objekte in einem einzigen Vorgang zu rendern. Godot bietet als Lösung für dieses Problem die Funktion Use Batching an. Diese Funktion kombiniert Sprites mit ähnlichem Material sowie andere 2D-Elemente wie Polygon2D oder CollisionPolygon2D, indem sie diese in einem einzigen Vertex-Buffer zusammenfasst. Auf diese Weise können viele einzelne Sprites mit einem einzigen Draw Call gerendert werden, was zu einer erheblichen Verringerung der Anzahl der Draw Calls und damit der API-Validierungslast führt. Dieser Vorgang erfordert kurzzeitig einen gewissen CPU-Aufwand, da in jedem Frame die Transformationen der Sprites abgerufen, die Vertex-Daten vorbereitet und in einen einzigen Buffer platziert werden müssen. Diese kurzzeitigen CPU-Kosten sind jedoch weitaus geringer als der Gewinn, der durch die deutlich geringere Anzahl an Draw Calls erzielt wird, und verbessern im Allgemeinen die gesamte CPU-Leistung.

Die Batching-Technik selbst birgt jedoch das Risiko, neue potenzielle Engpässe auf der CPU-Seite zu schaffen. Insbesondere wird eine Methode namens "Buffer Orphaning" verwendet. Bei dieser Methode kann derselbe Vertex-Buffer innerhalb eines Frames hunderte Male wiederverwendet werden. Dies ist eine effektive Methode, um die Kosten für Speicherzuweisung/Freigabe (Allocation/Deallocation) in Szenen mit dynamischem Inhalt zu reduzieren. Diese Technik kann jedoch aufgrund der Unklarheiten der OpenGL-Standards bezüglich der dynamischen Buffer-Verwaltung je nach Plattform und Grafiktreiber zu inkonsistenten Ergebnissen führen. Einige Treiber können diese ständige Wiederverwendung nicht optimieren und verursachen GPU-Treiber-Stalls, was die Leistung verschlechtern kann. Dies unterstreicht, dass eine Optimierung plattformspezifisches Verhalten zeigen kann und für jedes Projekt spezifische Tests durchgeführt werden müssen.

Beispielsweise zeigte ein Benutzerbericht, dass Batching auf einer integrierten Intel UHD 620 GPU unter Windows die Leistung von 60 FPS auf 45 FPS senkte, während auf derselben Hardware unter Linux keine negativen Auswirkungen festzustellen waren. Dies zeigt, dass 2D-Batching zwar ein leistungsstarkes Optimierungswerkzeug ist, Entwickler die Auswirkungen und potenziellen Risiken dieser Funktion jedoch sorgfältig abwägen müssen. Die CPU-Kosten für Batching bleiben in der Regel gering, solange nicht zehntausende Elemente verarbeitet werden, aber der Flaschenhals verschiebt sich oft auf die Ebene des Grafiktreibers während des Vertex-Buffer-Uploads.

Das auffälligste Beispiel für einen 2D-CPU-Engpass, den Entwickler leicht übersehen können, ist die Neuerstellung des Meshes in jedem Frame während der Animation in der Klasse Polygon2D. Dieser Zustand wird als "Mesh Churn" bezeichnet und kann enorme CPU-Kosten verursachen. In einem Testszenario führte das Freigeben (Free) des vorhandenen Meshes und das erneute Zuweisen (Erstellen) mit neuen Vertex-Daten in jedem Frame bei einem Polygon2D-Objekt, das mit AnimationPlayer animiert wurde, zu CPU-Kosten von 15,7 Millisekunden und einer Frame-Zeit von 35 ms/Frame vor der Optimierung, die die FPS von 28 auf 83 steigerte. Dies ist ein Beispiel dafür, wie wichtig es ist, die Leistung zu optimieren, da dies manchmal nicht intuitiv ist, sich aber auf die grundlegende Manipulation von Datenstrukturen konzentrieren muss.(116 1 2 1 11 1 1 1 1 1 1130)

Wenn die Optimierung darin besteht, die vorhandenen Vertex-Daten zu aktualisieren, anstatt ein neues Mesh zu erstellen, wurden Leistungssteigerungen von über 200 % beobachtet. Dies beweist, wie kritisch die Auswirkungen der Verwaltung von Datenstrukturen auf die CPU-Leistung sind. Cache-freundliche Datenstrukturen und Datenaktualisierungen ohne Abfallerzeugung sind ein grundlegendes Prinzip, um CPU-Engpässe insbesondere beim 2D-Rendering zu vermeiden.

Batching kann grundlegende strukturelle Fehler wie Mesh Churn möglicherweise nicht bekämpfen, da Batching darauf abzielt, bereits vorbereitete Vertex-Daten zu gruppieren, während Mesh Churn fehlerhaft handhabt, wie diese Daten in jedem Frame erstellt werden. Zusammenfassend lässt sich sagen, dass 2D-Render-Optimierungen ein Trade-off sind, der darauf abzielt, die Anzahl der Draw Calls zu reduzieren. Batching ist das gängigste Werkzeug für diesen Trade-off, hat jedoch plattformspezifische Kosten und Risiken. Das größte CPU-Risiko für Entwickler besteht darin, durch falsche Verwaltung von Datenstrukturen definitive Engpässe wie "Mesh Churn" zu erzeugen. Solche Fehler sollten durch Konzentration auf grundlegende Datenmanipulationspraktiken vermieden werden, anstatt durch nicht intuitive und komplex aussehende Optimierungen.

Optimierungstechnik

Ziel

CPU-Kosten (kurzfristig)

CPU-Kosten (langfristig)

Risiken und Plattformabhängigkeiten

Reduzierung der Anzahl der Draw Calls

Senkung der Kosten für API-Schnittstellenvalidierung und Zustandswechsel.

Niedrig (meist vernachlässigbar)

Sehr hoch (Leistungsgewinn)

Informationen nicht verfügbar

2D-Batching

Rendern von Sprites mit ähnlichem Material mit einem einzigen Draw Call durch Zusammenfassen in einem einzigen Vertex-Buffer.

Mittel (Vorbereitung und Zusammenfassung der Vertex-Daten)

Sehr hoch (Viel weniger Draw Calls)

Leistungsschwankungen je nach GPU-Treiber, Treiber-Stalls durch Buffer Orphaning.

Sprite-Atlas

Platzierung mehrerer Sprites auf einer einzigen großen Textur (Atlas).

Niedrig (Berechnung der Atlas-Koordinaten)

Hoch (Reduziert die Anzahl der Draw Calls durch Verringerung der Materialwechsel).

Einschränkungen der Atlasgröße, Komplexität der Atlas-Verwaltung.

Mesh Churn (Schlechte Praxis)

Freigabe und Neuerstellung des Meshes in jedem Frame aufgrund von Animationen oder dynamischen Änderungen.

Sehr hoch (Ständiges Free/Allocate von Vertex-Arrays)

Negativ (Enorme CPU-Kosten, FPS-Abfall)

Kann potenziell bei jedem 2D-Objekt der Szene auftreten, das animiert wird.

CPU-Engpässe im 3D-Renderprozess: Culling, Physik und Transformationsmanagement

Der 3D-Renderprozess umfasst eine weitaus komplexere Reihe CPU-intensiver Vorgänge als 2D, und Engpässe entstehen genau aus dieser Komplexität. Diese Engpässe lassen sich im Allgemeinen in drei Hauptkategorien unterteilen: Filtern unsichtbarer Objekte (Culling), physikalische Simulation und Synchronisation sowie die Verwaltung von Objekten und Lichtern in der Szene. Culling ist ein Prozess, der die Leistung erheblich steigert, indem er verhindert, dass unsichtbare Objekte an die GPU gesendet werden.

Diese Culling-Vorgänge können jedoch eine erhebliche CPU-Last verursachen, insbesondere wenn 3D-Szenen hunderttausende Objekte enthalten. Culling-Operationen werden in der Regel von der CPU ausgeführt. Zuerst werden Objekte außerhalb des Sichtfelds (Frustum) der Kamera herausgefiltert. Dieser Vorgang ist normalerweise sehr schnell, aber selbst bei großen Szenen ist die Anwendung auf hunderttausende Objekte kostspielig. Eine ausgefeiltere Technik, das Occlusion Culling, ist die effektivste, aber auch die teuerste Culling-Technik.

Godot führt diesen Vorgang auf der CPU unter Verwendung einer Software-Raytracing-Bibliothek namens Embree aus. Die Engine rasterisiert Occluder-Meshes, die mit OccluderInstance3D-Knoten markiert sind, auf der CPU-Seite in einen Low-Resolution-Depth-Buffer. Anschließend prüft sie anhand dieses Buffers, ob andere Objekte (Occludees) vollständig verdeckt sind. Wenn die AABB (Axis-Aligned Bounding Box) eines Objekts vollständig von einem Occluder verdeckt wird, wird es nicht gerendert. Dieser Vorgang ist besonders bei komplexen Occluder-Meshes und beweglichen Occludern in der Szene sehr CPU-intensiv.

Das Backen von Occludern und die Aktualisierung der BVH-Strukturen (Bounding Volume Hierarchy) für bewegliche Occluder in jedem Frame erzeugen eine erhebliche CPU-Last. Daher ist Occlusion Culling am besten für Innenräume mit mehreren Räumen geeignet, während es in offenen Szenen weniger nützlich ist. Zudem hängt der Nutzen von Occlusion Culling vom verwendeten Renderer ab. Der Forward+-Renderer führt bereits einen Depth-Prepass durch, um Overdraw zu reduzieren. Daher bietet Occlusion Culling zwar einen zusätzlichen Vorteil, aber der Mobile-Renderer (der keinen Depth-Prepass durchführt) erzielt einen größeren Leistungsunterschied.11 14 1619 111 1 2 1130

Der zweite große CPU-Engpass, die physikalische Simulation und deren Synchronisation mit dem Szenenbaum, ist besonders für dynamische Szenen von entscheidender Bedeutung. Dass Jolt Physics mit Godot 4.6 zum Standard wurde, war eine große Verbesserung sowohl in Bezug auf Geschwindigkeit als auch Stabilität. Jolt bietet fortschrittlichere Algorithmen zur Kollisionserkennung (GJK/EPA), stabilere Constraint-Solver und integrierte Multithreading-Unterstützung.

Dies führte in einer Szene mit 5.000 aktiven physikalischen Körpern zu einer 4,7-mal höheren FPS. Trotz des durch Jolt erzielten Leistungsgewinns kann jedoch der State-Sync-Mechanismus selbst, der zwischen dem PhysicsServer und dem Szenenbaum stattfindet, einen Engpass darstellen. Dieser Prozess, genannt flush_queries, löst für jedes aktive physikalische Objekt eine Callback-Funktion (_sync_body_state) aus, um die Transformationsinformationen der Knoten (z. B. RigidBody3D) auf der GDScript/C++-Seite zu aktualisieren.

Dies kann aufgrund der individuellen Callable-Aufrufe pro Objekt, insbesondere in großen Szenen mit mehr als 3.000 Objekten, eine CPU-Last erzeugen, die den Kosten der Physik-Step-Funktion entspricht.(23 2428 24 29 2428 24 2428 12 12 15) Die Lösung für dieses Problem liegt darin, anstelle einer API für individuelle Abfragen "Bulk-Query"-Funktionen hinzuzufügen, mit denen Sie die Transformationen, Geschwindigkeiten usw. aller aktiven Objekte als einen einzigen Rohdatensatz abrufen können. Dies würde die Callback-Kosten und die Überlastung der API-Schnittstelle eliminieren und eine signifikante CPU-Verbesserung bewirken. Entwickler können diesen automatischen State-Sync deaktivieren, indem sie body_set_state_sync_callback(Callable()) verwenden, und ihn manuell steuern.

Der dritte wichtige CPU-Engpass ist das Senden der Welt-Transformation jedes Objekts an die GPU in dynamischen Szenen und das Abgleichen jedes Objekts mit den für die Beleuchtung erforderlichen Lampen. Die Aktualisierung der Transformation eines dynamischen Objekts beinhaltet RenderingServer-Aufrufe wie instance_set_transform(). Dies kann die CPU-Auslastung des Schritts 'Preparing to Render 3D scene' in einer Szene mit mehr als 6.500 dynamischen Meshes um 50 % senken.

Dies bedeutet, dass es im Vergleich zum RID-basierten System unter dem Node-System effizienter sein sollte. Das Abgleichen von Objekten mit Lampen (Object-Light Pairing) erfolgt ebenfalls auf der CPU. Dass jedes Objekt eine Überlappungs- und Visibility-Mask-Prüfung mit jeder Lampe in der Szene durchführt, ist besonders in Szenen mit vielen Lampen und dynamischen Objekten ein kostspieliger Vorgang. Diese Berechnung verbraucht CPU-Zeit, obwohl sie nicht endgültig gerendert wird. Der Schlüssel zur Überwindung dieser Engpässe liegt in der Verwendung effizienterer APIs wie MultiMeshInstance.

MultiMeshInstance rendert tausende identische Meshes mit unterschiedlichen Transformationen wesentlich effizienter, wodurch die Darstellung von 6.500 dynamischen Meshes mit einer einzigen MMI eine Leistung erreichen kann, die dem Rendern einer einzelnen Ebene entspricht. Dies deckt sich mit dem Leistungsgewinn, der durch das Umgehen des SceneTree und die direkte Nutzung des RenderingServer erzielt wird.

Zusammenfassend lässt sich sagen, dass 3D-CPU-Engpässe aus breiteren und komplexeren Systemen wie Culling, Physik und dynamischem Objekt-/Lichtmanagement bestehen. Obwohl Occlusion Culling eine leistungsstarke Optimierung darstellt, beansprucht es die CPU stark. Physik und Transformationsmanagement bergen potenzielle Engpässe, insbesondere aufgrund von API-Einschränkungen wie flush_queries. Der Schlüssel zur Überwindung dieser Engpässe liegt in der Verwendung effizienterer APIs wie MultiMeshInstance und, falls möglich, im Umgehen des Szenenbaums durch die direkte Nutzung des RenderingServer. 15 15 15 27 22 27 27 22 2227

Engpass-Kategorie

Spezifischer Prozess

Quelle der CPU-Kosten

Optimierungsmethoden

Culling

Frustum Culling

Vergleich der AABBs von Objekten mit dem Frustum.

Einfach und schnell, effektiv selbst in großen Szenen.

Culling

Occlusion Culling

Rasterisierung mit Embree auf der CPU, Erstellung und Aktualisierung von Occluder-BVHs.

Teuerste Culling-Technik, verschlechtert sich bei komplexen Occluder-Meshes.

Physik und Synchronisation

Physikalische Simulation (Jolt)

Algorithmenkomplexität von Jolt und Multithread-Management.

Im Allgemeinen effizient, kann aber in großen Szenen dennoch eine erhebliche CPU-Last verursachen.

Physik und Synchronisation

State Sync (flush_queries)

Kosten für individuelle Callable-Callbacks für jedes aktive physikalische Objekt.

Bulk-Query-Funktionen (Vorschlag), Deaktivierung des automatischen Sync.

Objekt- und Lichtmanagement

Transformations-Update

Server-Aufrufe wie instance_set_transform() für jedes dynamische Objekt.

Verwendung von MultiMeshInstance, direkte Nutzung des RID-basierten RenderingServer.

Objekt- und Lichtmanagement

Objekt-Licht-Abgleich

Überlappungs- und Maskenprüfung jedes Objekts mit jeder Lampe in der Szene.

Reduzierung der Lampenanzahl, Einschränkung von Distant-/Shadow-Casting-Lampen, Verwendung von LOD.

Datenstrukturen, Skript-Performance und grundlegende Optimierungsstrategien

Die CPU-Performance der Godot-Engine hängt nicht nur von den Schritten innerhalb der Rendering-Pipeline ab, sondern auch grundlegend von den Prinzipien, die der Entwickler bei der Strukturierung der Szene und beim Schreiben der Skripte befolgt. Die Basis der CPU-Performance bilden neben den Algorithmen auch die Eignung der verwendeten Datenstrukturen für den CPU-Cache. Random-Memory-Access-Muster führen zu sogenannten "Cache Misses", was Leerlaufzeiten erzeugt, in denen die CPU darauf wartet, Daten aus dem langsamen RAM zu beziehen. Daher erhöht das Speichern von Daten im Speicher in sequenzieller und lokaler Form die Cache-Trefferrate und damit die Performance erheblich.

Obwohl die eigenen Service-APIs von Godot (zum Beispiel RenderingServer) solche Optimierungen auf der funktionalen Ebene behandeln, ist es für Entwickler von entscheidender Bedeutung, in ihrem eigenen Code (insbesondere in GDExtensions oder komplexen GDScript-Strukturen) cache-freundliche Datenstrukturen zu bevorzugen. Das auffälligste Beispiel für einen 2D-CPU-Engpass ist die Neuerstellung des Meshes in jedem Frame während einer Animation in der Klasse Polygon2D. Dies ist ein Beispiel dafür, dass die Optimierung der Performance manchmal kontraintuitiv ist, aber lebenswichtig sein kann, da sie sich auf die Manipulation grundlegender Datenstrukturen konzentrieren muss. Wenn die Optimierung in Form der Erstellung eines neuen Meshes anstatt der Aktualisierung der vorhandenen Vertex-Daten erfolgt, wurden Leistungssteigerungen von über 200 % beobachtet. 23 2428 12 15 2227 27 14 14 14 1130 11

Dies beweist, wie kritisch sich die Verwaltung von Datenstrukturen auf die CPU-Performance auswirkt. Ähnlich verhält es sich, wenn sich eine große Anzahl von Nodes (Tausende bis Zehntausende) in der Szene befindet; das Senden von _process()- und _physics_process()-Update-Benachrichtigungen durch den SceneTree an jeden Node stellt ebenfalls einen CPU-Kostenfaktor dar. Das Verwalten dieser Nodes außerhalb der Szene (Node.remove_child) oder durch Pausieren (set_process(false)) kann diese Kosten senken. Die Wahl der Skriptsprache ist ebenfalls ein Faktor, der die CPU-Performance erheblich beeinflusst. GDScript ist eher auf Benutzerfreundlichkeit als auf Geschwindigkeit ausgelegt. Das bedeutet, dass es über eine VM (Virtuelle Maschine) interpretiert wird und nicht direkt in Maschinencode kompiliert wird.

Dies bedeutet höhere CPU-Kosten im Vergleich zu C# und insbesondere C++-Modulen (GDExtensions). Da C# auf .NET basiert, bietet es dank JIT-Kompilierung eine gute Performance, kann jedoch Wartezeiten bei GC-Intervallen (Garbage Collection) aufweisen. C++ ist die schnellste Option, erfordert jedoch komplexere Bereitstellungsprozesse. Diese Performance-Unterschiede erfordern die richtige Wahl der Skriptsprache, insbesondere für häufig aufgerufene Schleifen oder kritische Algorithmen. Zur Steigerung der Performance von GDScript wurde die Verwendung eines Offline-Compilers vorgeschlagen. Dieser Ansatz eliminiert die zusätzlichen Kosten der VM, indem GDScript-Code beim Export in eine gemeinsam genutzte Bibliothek (z. B. .dll, .so) kompiliert wird, die mit dem C ABI kommunizieren kann. Dies ist jedoch ein zwingendes Hindernis für Plattformen wie iOS, WebAssembly und Konsolen, die keine dynamische Codegenerierung unterstützen. Dies verdeutlicht einen grundlegenden Gegensatz zwischen skriptbasierter Spieleentwicklung und hochperformanter In-Engine-Programmierung.

Im Lichte dieser grundlegenden Faktoren werden die wesentlichen Optimierungsstrategien, die Entwickler zur Verbesserung der CPU-Performance befolgen sollten, deutlich. Die erste besteht darin, den RenderingServer und PhysicsServer direkt zu nutzen, um die Kosten des SceneTree zu vermeiden. Das Housekeeping, das der SceneTree für jeden einzelnen Node durchführt (Lebenszyklus-Benachrichtigungen, Transform-Propagation usw.), kann in großen Szenen mit Tausenden von Objekten eine erhebliche CPU-Last erzeugen. Das Erstellen leichterer, RID-basierter Assets unter Verwendung des RenderingServer eliminiert diese Kosten weitgehend. Dieser Ansatz ist ideal, insbesondere für KI-Charaktere, Partikel oder andere hochdichte Objekte.

Die zweite Strategie besteht darin, integrierte GPU-gestützte Optimierungen wie MultiMeshInstance zu maximieren. Wenn Tausende identischer Modelle (Gras, Bäume, abgefeuerte Projektile usw.) gerendert werden müssen, bietet MultiMeshInstance die Möglichkeit, diese Objekte mit einem einzigen Draw Call zu rendern, was eine der effektivsten Methoden ist, um CPU-Engpässe wie Transform-Updates und Object-Light-Pairing in dynamischen Szenen zu vermeiden. Die dritte Strategie besteht darin, Datenstrukturen und Algorithmen cache-freundlich zu gestalten. Das Vermeiden der ständigen Neuerstellung von Vertex-Daten, das lineare Anordnen von Daten im Speicher und das Vermeiden unnötiger Speicherzuweisungen/-konvertierungen reduziert die Leerlaufzeit der CPU. 14 14 14 14 14 14 17 17 17 1422 22 2327

Schließlich ist Profiling der einzige zuverlässige Weg, um festzustellen, wo diese Optimierungen vorgenommen werden sollten. Der integrierte Profiler von Godot, insbesondere der Rendering-Abschnitt innerhalb der Frame-Zeit, kann zur Analyse renderbezogener CPU-Kosten verwendet werden. Es wurde jedoch berichtet, dass dieser Profiler die Wartezeiten einiger Dienste (Physik, Audio) nicht korrekt messen kann. Für tiefergehende Analysen kann das Timing eigener Codeblöcke mit hochauflösenden Uhren wie Time.get_ticks_usec() oder die Verwendung externer C++-Profiler wie Valgrind (Callgrind) genauere Ergebnisse liefern, um CPU-Hotspots zu identifizieren.

Profiling ist die konkretste Anwendung des Prinzips "Messen, bevor man optimiert" und stellt sicher, dass der Entwickler seine Zeit am effizientesten nutzt. Diese Strategien können wie eine Budgetverwaltung zur Steigerung der CPU-Performance betrachtet werden: Einsparung der Kosten des Szenenbaums, Erwerb effizienterer Ressourcen durch die Nutzung des RenderingServer und bestmögliche Nutzung jedes Taktzyklus des Prozessors durch das Schreiben von cache-freundlichem Code.

Optimierungsstrategie

Ziel

Anwendungsbereich

Erwarteter CPU-Gewinn

Vermeidung von SceneTree-Kosten

Eliminierung von Node-Lebenszyklus-Benachrichtigungen, Transform-Propagation und anderen Lasten des Szenenbaums.

Hochdichte Objekte (KI, Partikel, Projektile).

Einsparung der Kosten des Szenenbaums, besonders wichtig bei Tausenden von Nodes.

Verwendung von MultiMeshInstance

Optimierung des Renderns Tausender identischer Meshes mit unterschiedlichen Transforms.

Gras, Bäume, abgefeuerte Projektile, Instanzen.

Kosten für Transform-Updates und Object-Light-Pairing entfallen, die Anzahl der Draw Calls sinkt drastisch.

Cache-freundliche Gestaltung von Datenstrukturen

Minimierung der CPU-Leerlaufzeit durch Reduzierung von Cache Misses.

Gesamte Anwendung, insbesondere Vertex-Manipulation, Algorithmen.

Effizientere Nutzung jedes CPU-Taktzyklus, allgemeine Leistungssteigerung.

Optimierung der Skriptsprache

Eliminierung von CPU-Kosten durch zusätzliche Schichten wie VM oder GC.

Kritische Algorithmen, häufig aufgerufene Funktionen.

Performance-Reihenfolge: C++ (GDExtension) > C# > GDScript.

Fokussierung durch Profiling

Identifizierung der leistungsstärksten Bereiche (Hotspots) zur Steuerung der Optimierungsbemühungen.

Gesamte Anwendung.

Zeit und Mühe werden in die größten CPU-Engpässe investiert, unnötige Optimierungen werden vermieden.

Synthese: Komplexe Interaktionen, die die CPU-Performance bestimmen, und Zukunftsperspektiven

Als Ergebnis dieser eingehenden Analyse erweist sich die CPU-seitige Render-Architektur von Godot 4.7 als ein dynamisches System, das aus einer komplexen Interaktion verschiedener Faktoren besteht. 11 14 10 14 1014 1422 2327 1114 1417 1014. Das Verständnis der spezifischen CPU-Engpässe, das das Hauptziel der Untersuchung ist, lässt in Kombination mit einer allgemeinen Synthese einige grundlegende Schlussfolgerungen zu, die Entwicklern helfen werden, das Verhalten der Engine zu begreifen. Erstens steht die grundlegende Bilanz zwischen CPU und GPU im Zentrum der Render-Optimierungen.

Beim 2D-Rendering ist ein CPU-Engpass häufiger, da die Zeit, die die CPU für die Vorbereitung eines Frames benötigt (Draw Call Preparation, Culling usw.), meist länger ist als die Pixel-Shading-Zeit der GPU. In diesem Zusammenhang sind Techniken wie 2D-Batching ein effektiver Kompromiss, der CPU-Kosten in GPU-Kosten umwandelt. Beim 3D-Rendering ist die Situation komplexer; sowohl CPU- als auch GPU-Engpässe können aufeinanderfolgen. Während die CPU mit Culling-Operationen beschäftigt ist, die entscheiden, ob Objekte und Lampen gerendert werden sollen, ist die GPU mit der Tiefe und Beleuchtung der sichtbaren Polygone beschäftigt. Diese duale Beziehung zeigt, dass jede Optimierung Auswirkungen an anderer Stelle hat; zum Beispiel kann Occlusion Culling die CPU belasten und die Shading-Last der GPU reduzieren, aber dies kann in Fällen wie Polygon2D zu definitiven CPU-Engpässen wie Mesh-Churn führen. Zweitens ist jede Optimierung ein Kompromiss, und es ist entscheidend, das richtige Gleichgewicht basierend auf den Anforderungen der Szene des Entwicklers zu finden. Während 2D-Batching CPU-Kosten in GPU-Kosten umwandelt, belasten hohe LODs (Level of Detail) eher die GPU als die CPU. Transparente Objekte erfordern eine Back-to-Front-Renderreihenfolge und können daher nicht wie opake Objekte oder Materialien gebatcht werden, was sie von Natur aus teurer macht.

Diese Kompromisse können für jedes Projekt unterschiedliche Ergebnisse liefern, was bedeutet, dass die beste Strategie in der Regel experimentell unter Berücksichtigung des Inhalts der Szene, der Kapazität der Zielplattform und der ästhetischen Anforderungen des Benutzers bestimmt werden sollte. Abstraktionsschichten wie RenderingDevice und RenderingServer bieten Entwicklern zwar mehr Kontrolle und Performance, erzeugen aber tatsächlich weitere CPU-Kosten. Diese Schichten überlagern den SceneTree und verdeutlichen die Notwendigkeit eines Übergangs zu einem effizienteren RID-basierten Ansatz sowie die Komplexität, die dieser Ansatz mit sich bringt (z. B. manuelle Verwaltung der World-Transform). Dies zeigt, dass die durch Abstraktion gebotene Einfachheit ein tieferes architektonisches Verständnis erfordert, wenn die Performance kritisch ist.

Drittens hängt die CPU-Performance neben den Algorithmen auch von den grundlegenden Eigenschaften der verwendeten Datenstrukturen ab. Das Beispiel Polygon2D beweist, wie kritisch die richtige Datenmanipulation ist. Daten sollten aktualisiert statt ständig neu erstellt werden, sie sollten sequenziell im Speicher angeordnet sein und eine effiziente Nutzung des CPU-Caches ermöglichen. Diese Prinzipien sind besonders wichtig beim Schreiben von hochperformantem Code auf C++-Basis, wie z. B. GDExtensions. Darüber hinaus bringt die VM-basierte Struktur von Skriptsprachen wie GDScript im Vergleich zu C++-Modulen natürliche CPU-Kosten mit sich, was insbesondere für performancekritische Algorithmen die Verwendung von GDExtension erforderlich machen kann. Diese grundlegenden strukturellen Faktoren müssen vor oder gleichzeitig mit der Render-Pipeline selbst optimiert werden.

Betrachtet man die Zukunftsperspektiven, so zielen Fortschritte in der Render-Technologie darauf ab, die Rolle der CPU in der Render-Pipeline zu verändern. Arbeiten an fortgeschrittenen Optimierungen wie GPU-driven Rendering zielen darauf ab, dass die CPU alle für die Vorbereitung eines Frames erforderlichen Dispatches (für Objekte, Lampen, Pässe) an die GPU sendet und dann darauf wartet, dass die GPU den Vorgang abschließt. Diese Architektur könnte potenziell eine enorme Leistungssteigerung bewirken, indem die Leerlaufzeit der CPU minimiert wird. Dieser Ansatz erzwingt jedoch die Fortführung einiger Prozesse auf der CPU, wie den aktuellen CPU-driven transparenten Render-Pass, was einen neuen Engpass in der Architektur schaffen könnte. Solche architektonischen Änderungen würden zu einer groß angelegten Umstrukturierung und der Änderung bestehender Spielabläufe führen.

Zusammenfassend lässt sich sagen, dass die CPU-seitige Render-Architektur von Godot im Lichte der bereitgestellten Materialien eine flexible Struktur darstellt, die auf Plattformunabhängigkeit ausgelegt ist, aber gleichzeitig ein erhebliches Risiko für die CPU-Performance birgt, wie Synchronisationsfehler, plattformspezifisches Verhalten und komplexe Optimierungskompromisse. Durch das Verständnis dieser Architektur und dieser Engpässe können Entwickler die richtigen Strategien verfolgen, um effizientere, reibungslosere und leistungsstärkere Spiele zu erstellen.211 2327 1124 16 1619 522 22 1130 14 14

*Information : Die Bilder wurden von Google Gemini erstellt.

Godot4.7GDScriptDrawCallJoltPhysicsOptimizationVulkan
Deep Research Tool
Qwen (Alibaba Cloud)
Qwen (Alibaba Cloud)
Übersetzungstool
Google Gemini
Google Gemini

Teilen