UE5 Heterogeneous Multi-GPU: Unterschied zwischen den Versionen
Elaina (Diskussion | Beiträge) Nächsten Schritt auf Transfer- und SafeCopy-Timing gesetzt |
Elaina (Diskussion | Beiträge) Befehlsreferenz um ListWorkloads ergänzt und gestrafft |
||
| Zeile 291: | Zeile 291: | ||
== Test- und Diagnosebefehle == | == Test- und Diagnosebefehle == | ||
Die vorhandenen Compute-, Texture-, Raster-, StaticMesh-, World- und QueueProbe-Kommandos bleiben bestehen. Neu hinzugekommen ist: | |||
{| class="wikitable" | {| class="wikitable" | ||
| Zeile 296: | Zeile 298: | ||
! Zweck | ! Zweck | ||
|- | |- | ||
| <code>ExperimentalMultiGPU. | | <code>ExperimentalMultiGPU.ListWorkloads</code> | ||
| | | Listet aktive Workloads mit ID, Typ, gewünschter Zuweisung, Priorität, Auflösung, Zielrate, Lifecycle-Statistik und Worker-GPU-Timing auf. | ||
|} | |} | ||
Für den aktuellen Hauptpfad sind außerdem besonders relevant: | |||
<pre> | <pre> | ||
ExperimentalMultiGPU. | ExperimentalMultiGPU.SetSceneCaptureResolution <Width> <Height> | ||
ExperimentalMultiGPU.GetSceneCaptureResolution | |||
ExperimentalMultiGPU.TestSceneCaptureStaticMeshScene | |||
ExperimentalMultiGPU.StartSceneCaptureStaticMeshSceneStream [Hz] [MaxSuccessfulSubmits] [quiet] | |||
ExperimentalMultiGPU.StopSceneCaptureStaticMeshSceneStream | |||
ExperimentalMultiGPU.TestSkeletalMeshWorker | |||
ExperimentalMultiGPU.ListWorkloads | |||
</pre> | </pre> | ||
=== Texture Stream === | |||
== | |||
Grundsyntax: | Grundsyntax: | ||
<pre> | <pre>ExperimentalMultiGPU.StartTextureStream [Hz] [MaxSuccessfulSubmits] [quiet]</pre> | ||
ExperimentalMultiGPU. | |||
</pre> | |||
Die Hz-Angabe ist eine Ziel- beziehungsweise Request-Frequenz. <code>MaxSuccessfulSubmits</code> zählt nur angenommene Submits; Busy- und Failed-Versuche verbrauchen das Limit nicht. <code>quiet</code> unterdrückt Per-Job- und Busy-Logspam. | |||
=== QueueProbe === | |||
Grundsyntax: | |||
<pre> | <pre>ExperimentalMultiGPU.StartQueueProbe <Hz> <MaxSubmits> [quiet] <Mode> <Queue></pre> | ||
ExperimentalMultiGPU.StartQueueProbe | |||
</pre> | |||
Die Diagnosemodi <code>full</code>, <code>waitonly</code>, <code>signalonly</code>, <code>commandonly</code>, <code>barrieronly</code>, <code>barrierprivate</code> und <code>copybuffer</code> bleiben verfügbar. Als Queue können <code>graphics</code>, <code>copy</code>, <code>native</code>, <code>worker</code>, <code>worker-copy</code> und <code>worker-high</code> gewählt werden. Private native Queues dienen weiterhin nur der Diagnose und bilden keinen fertigen Experimental-/Direct-Modus. | |||
=== Benchmark-CVars === | === Benchmark-CVars === | ||
<code>r.VSync 0</code> deaktiviert VSync. <code>t.MaxFPS 30</code>, <code>60</code> oder <code>120</code> erzeugt reproduzierbare Frame-Coupling-Tests; <code>t.MaxFPS 0</code> entfernt das normale Framerate-Limit. | |||
== Aktueller nächster Schritt == | == Aktueller nächster Schritt == | ||
Version vom 14. September 2026, 18:24 Uhr
Hinweis: Projektstand: 12. September 2026. Letzter Engine-Checkpoint: 45d5c27 (Add live SceneCapture worker rendering pipeline) auf Branch mgpu-experimental. Dynamische SceneCapture-Auflösung, Scheduler-Korrektur und SkeletalMesh-ReferencePose gehören zum neueren, noch nicht als eigener Commit dokumentierten Working State. Der Commit wurde noch nicht gepusht. Die beschriebenen Ergebnisse sind ein experimenteller Proof of Concept für Unreal Engine 5.8 unter Windows und Direct3D 12.
UE5 Heterogeneous Multi-GPU ist ein experimentelles Projekt zur gemeinsamen Nutzung unterschiedlicher Grafikkarten in Unreal Engine 5. Eine leistungsfähige GPU übernimmt das normale Rendering. Weitere NVIDIA-, AMD- oder Intel-GPUs sollen als unabhängige Worker klar abgegrenzte Aufgaben bearbeiten.
Das Ziel ist ausdrücklich kein klassisches SLI oder CrossFire. Die Anwendung verteilt Arbeit selbst und tauscht nur benötigte Ergebnisse zwischen den GPUs aus.
| Engine / Plattform | Unreal Engine 5.8 Source Build / Windows / Direct3D 12 |
|---|---|
| Testsystem | Lenovo LOQ 17IRX10 |
| Primary | NVIDIA GeForce RTX 5060 Laptop GPU |
| Worker | Intel UHD Graphics |
| Zusätzliche Testhardware | Intel Arc A750 und Arc A380 als zusätzliche dGPU-Worker-Gegenchecks vorgesehen; belastbare Messwerte liegen noch nicht vor |
| Git-Stand | Letzter Commit 45d5c27 auf Branch mgpu-experimental; neuerer Working State mit Dynamic Resolution, Scheduler-Fix und SkeletalMesh-ReferencePose; nicht gepusht
|
| Windows/D3D12-Proof-of-Concept | Durchgängiger sichtbarer Worker-Rasterpfad von der laufenden UE-Welt über getaggte Static und Skeletal Meshes sowie eine echte SceneCapture2D-Kamera bis zur persistenten UE-Textur; dynamisch bis 3840×2160 und 4K60 mit 300/300 Ready-Frames nachgewiesen |
| Gesamtvision | etwa 30–35 % (Scheduler, echte Workloads, N-GPU und Vulkan noch offen) |
Grundprinzip
Intel Worker GPU -> MainTextureCS + PatternPhase -> persistente Worker-Textur -> persistenter Shared Cross-Adapter Buffer -> monotone Shared Fence / Queue Wait -> persistente UE-eigene FTextureRHIRef -> stabile UExperimentalMultiGPUTexture RTX Primary GPU -> normales UE5-Rendering -> TPS-Material zeigt das Worker-Ergebnis sichtbar an
Pro Worker ist maximal ein Runtime-Job gleichzeitig in flight. Backpressure lehnt weitere Requests als Busy ab, bis der Slot nach der Primary-Completion wieder sicher verwendbar ist.
Jeder Worker besitzt ein eigenes ID3D12Device, eigene Queues, Command Lists und Ressourcen. Ein späterer Scheduler soll Rechenleistung, Fähigkeiten, Transferkosten und gewünschte Aktualisierungsrate bewerten. Vorgesehene Aufgaben sind beispielsweise SceneCapture2D, Spiegel, CCTV, Minimap, Compute oder Niagara.
Verifizierter Entwicklungsstand
Worker-Compute
Ein echter, von Unreal kompilierter Global Compute Shader läuft auf der Intel UHD, während Unreal regulär über die RTX rendert. Der Test verdoppelt 64 Ganzzahlen auf dem Worker; 64 von 64 Ergebnissen wurden korrekt verifiziert. Damit ist echte GPU-Arbeit auf einem zweiten, herstellerfremden Device nachgewiesen.
Zusätzlich läuft MainTextureCS auf der Intel-GPU. Der synchrone Debugtest erzeugt eine 64 × 64 Pixel große RGBA8-Textur; alle 4096 shader-generierten Pixel wurden korrekt verifiziert. Der normale Runtime-Pfad verwendet inzwischen einen asynchronen 128×96-Test. Der Shader liest seine Dimensionen mit GetDimensions(), schützt überhängende Threads durch einen Bounds-Check und wird mit Dispatch(16,12,1) ausgeführt. PatternPhase erzeugt für jeden akzeptierten Submit deterministisch einen neuen sichtbaren Inhalt. Abgewiesene Busy-Requests erhöhen die Phase nicht und erzeugen daher keine Lücken.
Cross-Adapter-Speicher und Synchronisation
- RTX und Intel öffnen denselben D3D12 Cross-Adapter Heap.
- Ein gemeinsamer Buffer wurde mit 64 Testwerten erfolgreich geprüft.
- Ein persistenter Shared Fence synchronisiert die Queues direkt von GPU zu GPU. Seine Werte steigen pro Job monoton: WorkerReady/PrimaryComplete verwenden die Paare 1/2, 3/4, 5/6 und so weiter.
- Die CPU vermittelt die GPU-Abhängigkeit nicht; Worker- und Primary-Queue verwenden
SignalundWaitdirekt auf der GPU. - Der separate Readback-Verifikationstest darf weiterhin blockieren. Der normale Runtime-Pfad wartet dagegen nicht auf GPU-Leerlauf.
Texturtransport
Direkte Cross-Adapter-Texturen sind auf dem Testsystem nicht beidseitig verfügbar: Die Intel UHD meldet Unterstützung für Row-Major Cross-Adapter Textures, die RTX 5060 Laptop GPU nicht. Deshalb verwendet das Projekt einen allgemeineren Buffer-Fallback.
Intel MainTextureCS -> persistente Worker-Textur -> CopyTextureRegion -> persistenter Shared Cross-Adapter Buffer -> Worker signalisiert WorkerReady (1, 3, 5, ...) -> UE-Primary-Queue wartet auf WorkerReady -> direkte Kopie in persistente UE-eigene FTextureRHIRef -> Primary signalisiert PrimaryComplete (2, 4, 6, ...)
Der robuste Normal-Pfad kopiert die Worker-Textur über den Shared Buffer direkt in eine von Unreal erzeugte FTextureRHIRef. Breite, Höhe und Row Pitch werden dynamisch aus Worker-Ressource und Transport-Metadaten übernommen. Der erfolgreiche 128×96-RGBA8-Test verwendet 512 Byte Row Pitch und 49.152 Byte Transfergröße. Die Output Registry trennt Persistent-, Pending- und Ready-Zustände sowie Inhaltsgeneration und Texturidentität. Dadurch kann eine verspätete Completion keinen neueren Output zurückrollen.
Non-blocking Runtime-Pfad
Die produktive Testlogik ist aus dem Konsolenkommando in eine gekapselte NormalPath-API verschoben. SubmitWorkerTextureComputeJob() reicht den Worker-Dispatch ohne CPU-Wait ein. QueueNormalTextureTransfer() reiht Wait, Kopie und Completion-Signal in Unreals D3D12-Kontext ein. Der normale MainTextureCS-Pfad enthält weder CPU-Fence-Waits noch Readback, Map oder Pixelvergleich. TickNormalTextureTransfers() prüft die Primary-Completion non-blocking über einen Runtime-Pump; RHIEndFrame() bleibt als Fallback erhalten.
Eine passende Primary-FTextureRHIRef wird pro Worker und Konfiguration persistent gehalten. Nach PrimaryComplete kann der nächste Job dieselbe Resource erneut verwenden; dabei führt Unreal die Übergänge SRVMask -> CopyDest -> SRVMask aus. Eine backend-neutrale RHI-Provider-API reicht die Ready-Textur an die Engine weiter. UExperimentalMultiGPUTexture hält eine stabile UTexture samt TextureReference und bindet die persistente FTextureRHIRef ohne zusätzlichen Pixel-Copy. Eine native D3D12-Resource wird weiterhin nicht blind als UE-Textur gewrappt, weil Unreals Resource-State-Buchhaltung den tatsächlichen nativen Zustand sonst nicht sicher kennt.
Sichtbarer TPS-Consumer
AExperimentalMultiGPUDisplayActor zeigt die Worker-Ausgabe im Third-Person-Testlevel über den Materialparameter MultiGPUTexture an. Damit ist der Output nicht mehr nur über Logs oder Readback nachgewiesen: Die Intel UHD erzeugt fortlaufend die Texturinhalte, während die RTX die Spielwelt und das Material rendert. TextureConsumerCS kann Ready-Ausgaben auf der Primary zusätzlich als SRV lesen und fünf quantisierte Samples im Debugpfad prüfen.
Lebensdauer und automatisches Cleanup
Der Normal-Pfad verwendet pro Worker genau einen persistenten Runtime-Slot. Worker-Textur, Root Signature, PSO, Descriptor Heap, Command Allocator/List und Worker-Fence werden nach der ersten kompatiblen Initialisierung wiederverwendet. Dasselbe gilt für Shared Heap/Buffer/Fence und die Transport-Command-Objekte. Der In-Flight-Job trägt nur noch die nötigen Job-, Generation- und Fence-Metadaten.
Runtime Request -> Completion non-blocking prüfen -> falls Slot frei: Worker Dispatch -> WorkerReady-Fence signalisieren -> Primary Wait, SafeCopy und PrimaryComplete-Signal -> Output Pending -> Runtime-Pump oder RHIEndFrame prüft GetCompletedValue() -> Output Ready publizieren -> Slot wiederverwendbar
Backpressure erlaubt maximal einen echten In-Flight-Job pro Worker. Ein Busy-Request setzt keine GPU-Arbeit ab, setzt keine Ressourcen zurück und erhöht PatternPhase nicht. Worker-Fence-Werte steigen mit jedem akzeptierten Submit 1, 2, 3 und so weiter; der persistente Shared Fence verwendet fortlaufend die Paare 1/2, 3/4, 5/6. Fehler nach Übergabe von Ressourcen an die GPU führen zu sicherem Retire beziehungsweise Retention statt zu vorzeitigem Reuse.
Texture Stream und Benchmarking
Der projektseitige Texture Stream akzeptiert eine beliebige positive, endliche Zielrate. Punkt und Komma sind als Dezimaltrenner möglich. Optional lässt sich die Zahl erfolgreicher Submits begrenzen; Busy- oder Failed-Versuche verbrauchen dieses Limit nicht. Der Quiet-Modus unterdrückt per-Job-Ausgaben, behält aber Start, echte Fehler und die Abschlussstatistik bei.
Die Statistik unterscheidet Timer-Ticks, angenommene Submits, Busy/Failed, Ready, In-Flight und die jeweiligen effektiven Raten. Diagnosezeitpunkte erfassen unter anderem RequestAccepted, RenderCommandExecution, WorkerSubmit, NormalTransferQueued und ReadyObserved.
| Test | Ergebnis | Aussage |
|---|---|---|
| 5 Hz, 20 Submits | etwa 5 Ready/s, 20/20, kein Busy | Entspannter Single-Slot-Betrieb |
| 1000 Hz, 100 Submits, quiet | 100/100 Ready, 0 Fehler, etwa 58 Ready/s | Backpressure begrenzt den Durchsatz sicher |
1000 Hz bei t.MaxFPS 30
|
etwa 28–29 Ready/s trotz rund 982 Timer-Ticks/s | Primary-/SafeCopy-/Ready-Kette ist effektiv framegekoppelt |
Ein zusätzlicher non-blocking Aufruf von ImmediateFlush(DispatchToRHIThread) erreicht hohe Render-Command-Raten, hebt die gemessene Frame-Kopplung aber nicht auf. Im 30-FPS-Test lag der RHIEndFrame-spezifische Completion-Takt praktisch exakt bei 30 Hz. Das aktuelle Problem ist damit eingegrenzt: Nicht der Stream-Timer, sondern die Primary-RHI-/D3D12-Submission der SafeCopy-Kette bestimmt die sichtbare Ready-Rate.
Queue-Submission-Diagnose
MultiGPUD3D12QueueProbe untersucht die Submission unabhängig vom sichtbaren Texturpfad. Neben full, waitonly und signalonly existieren inzwischen die Diagnosemodi computeonly, commandonly, barrieronly, barrierprivate und copybuffer. Als Queues stehen graphics, copy, native, worker, worker-copy und worker-high zur Verfügung. Graphics und Copy verwenden RHIRunOnQueue(..., false) auf UE-verwalteten Primary-Queues; die übrigen Varianten isolieren private beziehungsweise bereits vorhandene native D3D12-Queues.
Der Probe misst Request, Worker-Submit, RHIRunOnQueue-Aufruf und -Callback, Primary-Execute, Fence-Completion sowie Ready bis zum nächsten akzeptierten Submit. Er fügt keine CPU-Waits, Readbacks, Maps oder BlockUntilGPUIdle ein und beschreibt keine Textur des sichtbaren Normal-Pfads.
| Test | Ergebnis | Befund |
|---|---|---|
| UE Graphics, signal-only, 30 FPS | 33,319 ms PrimaryExecute bis Ready | UE-verwaltete Graphics-Submission skaliert mit der Frame-Dauer |
| UE Copy, signal-only, 30 FPS | 32,593 ms PrimaryExecute bis Ready | Auch die UE-verwaltete Copy Queue ist framegekoppelt |
| Private Primary-DIRECT-Queue, signal-only | etwa 0,014 ms Queue/Fence-Latenz; nach Lifecycle-Fix rund 999 Ready/s bei 30 FPS | Native Queue und Scheduler sind vollständig vom Frame entkoppelt |
| Worker signal-only / leere Command List | jeweils rund 1000 Ready/s bei 30 FPS | Worker-Queue, Fence und reine Submission sind nicht framegekoppelt |
Worker CopyBufferRegion, 30 / 60 / 120 FPS
|
29,41 / 58,25 / 113,08 Ready/s | Bereits eine echte 256-Byte-GPU-Kopie folgt im UE-Prozess dem Frame-Raster |
| Worker COPY Queue / HIGH Priority, 30 FPS | jeweils rund 29,42 Ready/s | Queue-Typ und Priorität beseitigen die Kopplung nicht |
| Standalone-D3D12 auf derselben Intel UHD | 92.956 Copy-Jobs/s; 0,007 ms durchschnittliche Fence-Latenz | Die Kopplung ist UE-Prozess-/Scheduling-spezifisch, keine allgemeine Intel-, WDDM- oder D3D12-Grenze |
Der RHIRunOnQueue-Callback selbst erreicht den D3D12-Submission-Thread typischerweise bereits nach etwa 0,02–0,05 ms; die Verzögerung entsteht danach in der verwalteten Queue-/Submission-Reihenfolge. SubmitCommandsHint() ist in UE 5.8 nur ein veralteter Alias für ImmediateFlush(DispatchToRHIThread) und stellt daher keinen stärkeren Submission-Mechanismus dar.
Der native Test ist ein Architekturbeweis, noch kein Experimental- oder Direct-Modus. Die zunächst gemessene Verzögerung von rund 32 ms lag zwischen der In-Flight-Freigabe und dem nächsten Request auf dem Render Thread. Für signalonly/native wurde der sichere Completion-/Submit-Lifecycle deshalb in den frameunabhängigen Scheduler verlegt. Danach erreichten zwei 100-Job-Läufe bei t.MaxFPS 30 rund 999 bis 1029 Ready/s, ohne Busy oder Fehler.
Der anschließende full native-Test lokalisierte die verbleibende Kopplung auf echte Worker-GPU-Arbeit: WorkerSubmit bis WorkerReady benötigte bei 30, 60 und 120 FPS ungefähr 33,29, 16,93 und 8,45 ms; WorkerReady bis PrimaryReady war danach praktisch sofort. Signal-only und eine leere Command List waren schnell, Resource Barriers und eine 256-Byte-Kopie dagegen framegebunden. Ein eigenständiger D3D12-Microbenchmark auf derselben Intel UHD erreichte außerhalb von Unreal bei derselben Kopie rund 92.956 Jobs/s mit 0,007 ms mittlerer Fence-Latenz. Die genaue Ursache im UE-Prozess beziehungsweise dessen GPU-Scheduling-Kontext bleibt offen und wird vorerst bis zum Gegencheck mit der vorhandenen Intel Arc A380 geparkt.
Worker-Rasterpfad und RasterStream
ExperimentalMultiGPU.TestRasterWorker weist erstmals echte Rasterizer-Arbeit auf der Intel-Worker-GPU nach. RasterWorkerVS und RasterWorkerPS werden über Unreals Global-Shader-Pfad kompiliert und für ein natives Worker-Graphics-PSO verwendet. Ein persistentes 128×96-RGBA8-RenderTarget besitzt RTV, leere Root Signature und eine DIRECT-Command-List. Der Vertex Shader erzeugt drei Vertices über SV_VertexID; der Pixel Shader schreibt interpolierte RGB-Farben.
Der Job wechselt das RenderTarget von COPY_SOURCE nach RENDER_TARGET, führt Clear und DrawInstanced(3,1,0,0) aus und wechselt anschließend zurück nach COPY_SOURCE. Danach verwendet er unverändert den robusten Normalpfad aus Cross-Adapter-Shared-Buffer, Primary SafeCopy, persistenter FTextureRHIRef, UExperimentalMultiGPUTexture und DisplayActor. Der Runtime-Test meldete einen akzeptierten Submit und RasterWorker READY für Generation 1; die 128×96-UTexture wurde erfolgreich gebunden.
Der einmalige Test wurde außerdem zu einem frei konfigurierbaren RasterStream ausgebaut. Ein sichtbarer Lauf mit 10 Hz und 50 erfolgreichen Submits endete mit 50 Ready-Ausgaben, 0 Busy und 0 Fehlern bei etwa 9,90 Ready/s. Primary-Textur und Materialbindung blieben dabei persistent.
Raster3D mit echten Vertex- und Indexbuffern
Der Worker besitzt inzwischen einen kleinen eigenständigen 3D-Rasterpfad mit perspektivischer Kamera, Model-View-Projection-Matrix als Root Constants, D32_FLOAT-Depth-Buffer und Depth-Test. Der finale Testwürfel verwendet 24 POSITION/COLOR-Vertices, 36 uint16-Indices und DrawIndexedInstanced(36,1,0,0,0); sechs getrennte Seitenfarben machen Ausrichtung und Rotation sichtbar nachvollziehbar.
Die statischen Meshdaten werden einmalig über private Upload-Buffer in DEFAULT-Heaps übertragen. Der Init-Fence wird non-blocking über GetCompletedValue() beobachtet. Der eigentliche Runtime-Renderpfad enthält weiterhin keine CPU-Waits und keine Maps.
UE-Static-Meshes auf der Worker-GPU
StaticMeshWorker extrahiert echte LOD0-Geometrie aus UStaticMesh-Assets. POSITION und NORMAL sowie uint32-Indices werden in POD-Daten überführt, einmalig asynchron in eigene Worker-VB/IB hochgeladen und anschließend per DrawIndexedInstanced gerendert. Assetpfade sind dynamisch; Auto-Fit richtet Kamera beziehungsweise Modellmaßstab an den Bounds aus.
Sichtbar korrekt getestet wurden:
| Asset | Vertices | Indices |
|---|---|---|
| Sphere | 559 | 2880 |
| Cube | 54 | 144 |
| Cylinder | 334 | 1536 |
Multi-Mesh Worker-Szene
StaticMeshScene rendert Sphere, Cube und Cylinder gemeinsam in einen Color-/Depth-Frame. Der verifizierte Test umfasst insgesamt 947 Vertices und 4560 Indices, drei DrawIndexedInstanced-Aufrufe und nur einen anschließenden Cross-Adapter-/Normal-SafeCopy-Transfer. Damit ist nicht mehr nur ein synthetisches Primitive, sondern mehrere echte UE-Meshes mit gemeinsamer Tiefenprüfung im Worker-Frame sichtbar nachgewiesen.
WorldStaticMeshScene
ExperimentalMultiGPUWorldStaticMeshScene.cpp sammelt im Engine-Layer bis zu acht sichtbare UStaticMeshComponents von Actors mit dem Tag MultiGPUWorker. Meshdaten und World-Transforms werden im Game Thread in reine POD-Strukturen und 4×4-Matrizen kopiert. D3D12RHI erhält keine UObject-Pointer. Mehrere Instanzen desselben Meshes teilen den persistenten Worker-Mesh-Cache und unterscheiden sich nur durch Modelmatrix und Draw Call.
Der Pfad wurde inzwischen sichtbar im PIE-Level verifiziert. Drei getaggte Static-Mesh-Actors erschienen mit ihren tatsächlichen relativen Positionen, Rotationen und Skalierungen im Worker-Bild. Die zentrale Koordinatenumrechnung lautet:
Worker (X, Y, Z) = (UE Y, UE X, -UE Z)
Die Transformation besitzt die Determinante +1 und ist damit keine Spiegelung. Das notwendige horizontale Spiegeln erfolgt ausschließlich auf der Presentation-Plane, nicht im Worker-Renderpfad.
Kontinuierlicher WorldStaticMeshSceneStream
StartWorldStaticMeshSceneStream erfasst die getaggte Szene fortlaufend im Game Thread. Reine Änderungen an Transform oder Kamera lösen keinen erneuten Mesh-Upload aus. Hinzugekommene, entfernte, unsichtbare oder umgetaggte Components werden im nächsten Snapshot berücksichtigt. Single-Slot-Backpressure verhindert einen wachsenden Rückstau.
Ein verifizierter 30-Hz-Lauf endete nach 31,184 Sekunden mit 835 Ticks, 691 angenommenen Submits, 144 Busy, 0 Fehlern und 691 Ready-Frames. Die effektive Ready-Rate betrug 22,16 Hz. Dieser Test belegt die funktionale Live-Aktualisierung; die Rate war noch kein Optimierungsziel.
SceneCapture2D als Worker-Kamera
TestSceneCaptureStaticMeshScene verwendet eine echte ASceneCapture2D beziehungsweise USceneCaptureComponent2D aus der laufenden PIE-Welt als Kameraquelle. Der empfohlene Editor-Weg ist das Component Tag MultiGPUWorkerCamera auf der CaptureComponent2D; ein gleichnamiges Actor-Tag bleibt nur als Legacy-Fallback.
Ausgelesen werden World Location, Rotation, Forward/Right/Up, FOVAngle und ProjectionType. Orthografische Projektion wird derzeit sauber abgelehnt. Die SceneCapture2D liefert ausschließlich Kameradaten: Ihr UE-RenderTarget wird weder gelesen noch kopiert. Die Intel-Worker-GPU rendert die Geometrie selbst. Kamera und Actors werden gemeinsam mit SceneScale = 0.01 in Worker-Koordinaten überführt; Auto-Fit und Scene-Recentering sind in diesem Modus deaktiviert.
Die 128×96-Worker-Projektion verwendet ein Seitenverhältnis von 4:3 und interpretiert FOVAngle als horizontales FOV. Nach den Orientierungstests wird nur die Anzeige über SetRelativeScale3D(FVector(-4.0f, -3.0f, 1.0f)) korrigiert; dies entspricht U' = 1-U und V' = 1-V.
Live SceneCaptureStaticMeshSceneStream
StartSceneCaptureStaticMeshSceneStream liest Kamera-Transform, Kamerabasis, FOV und aktuelle Actor-Transforms pro Snapshot neu im Game Thread. Der Worker erhält weiterhin ausschließlich POD-Daten, verwendet den persistenten Mesh-Cache sowie ein gemeinsames Color- und D32-Depth-Target und führt pro akzeptiertem Frame genau einen Cross-Adapter-/Normal-SafeCopy-Transfer aus.
Der Quiet-Test
ExperimentalMultiGPU.StartSceneCaptureStaticMeshSceneStream 30 300 quiet
lieferte in 10,084 Sekunden 301 Ticks, 300 Submits, 1 Busy, 0 Fehler und 300 Ready-Frames. Die effektive Ready-Rate betrug 29,75 Hz. Bewegungen der Kamera und Actors sowie FOV-Änderungen erschienen live auf der Display-Plane. Damit ist erstmals ein kleiner SceneCapture-/CCTV-artiger Worker-Kanal vollständig funktional nachgewiesen.
Dynamische SceneCapture-Auflösung
Die SceneCapture-Ausgabe ist nicht mehr fest auf 128×96 Pixel begrenzt. ExperimentalMultiGPU.SetSceneCaptureResolution <Width> <Height> setzt ganzzahlige Dimensionen von 1 bis 4096; GetSceneCaptureResolution zeigt die aktive Einstellung. 128×96 bleibt der Standard. Während ein SceneCapture-Stream läuft, wird ein Auflösungswechsel absichtlich abgelehnt, damit keine In-Flight-Ressourcen ausgetauscht werden.
Color Target, D32-Depth-Buffer, Row Pitch, Shared-Buffer-Transport, SafeCopy, persistente Primary-Textur und UExperimentalMultiGPUTexture übernehmen die gewählten Dimensionen. Projektion und Display-Plane verwenden das echte Seitenverhältnis. Die bestätigte Anzeigeorientierung U' = 1-U und V' = 1-V bleibt eine reine Presentation-Korrektur; Worker-Basis und Kameramathematik ändern sich nicht.
Scheduler-Korrektur bei SceneCapture
Ein 1920×1080-Test mit 25 Hz erreichte zunächst trotz fehlerfreier Frames nur 13,90 Ready-Hz und erzeugte 222 Busy-Ticks. Ursache war kein GPU-Limit, sondern ein Polling-Aliasing: Eine frühe Game-Thread-Prüfung konnte den Timer-Slot verwerfen, obwohl die Completion kurz darauf im CoreTicker/RHI-Pump sichtbar geworden wäre.
Die frühe IsRuntimeTextureUpdateInFlight()-Prüfung wurde für diesen Stream entfernt. Der Pending-Request bleibt als Backpressure-Gate erhalten; die maßgebliche Busy-Entscheidung fällt nun erst im Render Command nach TickNormalTextureTransfers(nullptr, true). Single-Slot, POD-Snapshots und non-blocking GetCompletedValue() bleiben erhalten. Es wurden weder CPU-Waits noch zusätzliche Threads eingeführt.
Nach der Korrektur erreicht 1080p bei 25 Hz 24,79 Ready-Hz mit 1 Busy; bei 30 Hz sind es 29,43 Ready-Hz mit 4 Busy.
Skalierung bis 4K60
Die Pipeline wurde von 640×360 bis 3840×2160 getestet. Bei 640×360/30 Hz und 1280×720/30 Hz wurden 29,85 beziehungsweise 29,82 Ready-Hz ohne Busy erreicht. 1920×1080/60 Hz lieferte 59,40 Ready-Hz ohne Busy, 2560×1440/60 Hz 59,21 Ready-Hz mit 1 Busy und 3840×2160/60 Hz 59,02 Ready-Hz mit 2 Busy.
Ein zusätzlicher 4K60-Stresstest mit acht Static-Mesh-Instanzen und drei gecachten Unique Meshes erreichte in 5,049 Sekunden 300 von 300 Ready-Frames, 0 Busy, 0 Fehler und 59,42 effektive Ready-Hz. Acht Draw Calls, gemeinsamer Depth Buffer, Cross-Adapter-Transport und SafeCopy liefen dabei im vorhandenen Single-Slot. Das ist ein Stabilitätsnachweis der aktuellen Mini-Szene, kein allgemeiner GPU-Benchmark.
Skeletal Mesh in Reference Pose
ExperimentalMultiGPU.TestSkeletalMeshWorker erweitert den Geometriepfad auf USkeletalMeshComponent. Der Engine-Layer sucht deterministisch die erste sichtbare, getaggte Komponente und extrahiert LOD0-Positionen, Normalen, Indices und Render Sections. D3D12RHI erhält wie bisher ausschließlich POD-Geometrie und Modelmatrizen.
Der sichtbare Test mit SKM_Quinn_Simple rendert Quinn in der Reference Pose mit 45.993 Vertices, 261.840 Indices, zwei Sections und zwei Draw Calls. Die beiden Sections umfassen 35.612 und 51.668 Dreiecke. Worker-VB/IB, gemeinsamer D32-Depth-Buffer, dynamische Auflösung, Cross-Adapter-Transport und Normal/SafeCopy werden wiederverwendet.
Eine Reference Pose ist die unverformte Ausgangshaltung des Skeletal Meshes. Bone-Matrizen, Skin Weights, laufende Animationen, Morph Targets und Cloth sind noch nicht angebunden. Der nächste Geometrieschritt ist daher echtes Worker-Skinning.
Test- und Diagnosebefehle
Die vorhandenen Compute-, Texture-, Raster-, StaticMesh-, World- und QueueProbe-Kommandos bleiben bestehen. Neu hinzugekommen ist:
| Befehl | Zweck |
|---|---|
ExperimentalMultiGPU.ListWorkloads
|
Listet aktive Workloads mit ID, Typ, gewünschter Zuweisung, Priorität, Auflösung, Zielrate, Lifecycle-Statistik und Worker-GPU-Timing auf. |
Für den aktuellen Hauptpfad sind außerdem besonders relevant:
ExperimentalMultiGPU.SetSceneCaptureResolution <Width> <Height> ExperimentalMultiGPU.GetSceneCaptureResolution ExperimentalMultiGPU.TestSceneCaptureStaticMeshScene ExperimentalMultiGPU.StartSceneCaptureStaticMeshSceneStream [Hz] [MaxSuccessfulSubmits] [quiet] ExperimentalMultiGPU.StopSceneCaptureStaticMeshSceneStream ExperimentalMultiGPU.TestSkeletalMeshWorker ExperimentalMultiGPU.ListWorkloads
Texture Stream
Grundsyntax:
ExperimentalMultiGPU.StartTextureStream [Hz] [MaxSuccessfulSubmits] [quiet]
Die Hz-Angabe ist eine Ziel- beziehungsweise Request-Frequenz. MaxSuccessfulSubmits zählt nur angenommene Submits; Busy- und Failed-Versuche verbrauchen das Limit nicht. quiet unterdrückt Per-Job- und Busy-Logspam.
QueueProbe
Grundsyntax:
ExperimentalMultiGPU.StartQueueProbe <Hz> <MaxSubmits> [quiet] <Mode> <Queue>
Die Diagnosemodi full, waitonly, signalonly, commandonly, barrieronly, barrierprivate und copybuffer bleiben verfügbar. Als Queue können graphics, copy, native, worker, worker-copy und worker-high gewählt werden. Private native Queues dienen weiterhin nur der Diagnose und bilden keinen fertigen Experimental-/Direct-Modus.
Benchmark-CVars
r.VSync 0 deaktiviert VSync. t.MaxFPS 30, 60 oder 120 erzeugt reproduzierbare Frame-Coupling-Tests; t.MaxFPS 0 entfernt das normale Framerate-Limit.
Aktueller nächster Schritt
Als Nächstes wird die GPU-Zeit des Transfers vom Worker-ColorTarget in den Shared Cross-Adapter Buffer separat gemessen. Danach folgt das Timing der Primary-SafeCopy. Erst aus den getrennten Blöcken Worker-Raster, Cross-Adapter-Transfer und Primary-Integration soll ein belastbares Kostenmodell V1 entstehen. Dieses bildet anschließend die Grundlage für automatische Primary-/Worker-Zuweisungen; die aktuelle Registry plant bewusst noch nichts selbst.
Historische Vorläufer
Die Grundidee heterogener Multi-GPU-Nutzung ist nicht neu. Bereits mit der Einführung von Direct3D 12 entstanden mehrere Versuche, integrierte und dedizierte GPUs gemeinsam zu verwenden. Eine allgemein einsetzbare Lösung für moderne UE5-Spiele hat sich daraus jedoch nicht entwickelt.
Microsoft und Epic: UE4 Elemental Demo (2015)
Microsoft zeigte eine angepasste Unreal-Engine-4-Version der Elemental-Demo, bei der eine NVIDIA-GPU und eine Intel-iGPU gemeinsam arbeiteten. Das erklärte Ziel war, ansonsten ungenutzte Grafikleistung in Hybrid-Systemen zu verwenden. Dieser Versuch ist der Grundidee des Projekts besonders ähnlich, blieb aber eine technische Demonstration.
Intel: D3D12 Multi-Adapter Sample (2015)
Intels Beispiel kombinierte eine GeForce 840M mit Intel HD Graphics 5500. Beide GPUs berechneten getrennte Teile einer einfach parallelisierbaren Raytracing-Szene. Cross-Adapter-Ressourcen und Shared Fences führten die Ergebnisse anschließend zusammen. Intel berichtete in diesem Test von nahezu halbierter Framezeit, warnte jedoch ausdrücklich davor, das Ergebnis unmittelbar auf reale Spiele zu übertragen.
Ashes of the Singularity
Die Nitrous Engine von Ashes of the Singularity setzte Direct3D-12-Explicit-Multiadapter in einem veröffentlichten Spiel ein. Dabei konnten auch unterschiedliche GPU-Modelle und Hersteller zusammenarbeiten. Das war ein wichtiger Praxisnachweis, blieb in der Spielebranche aber eine seltene Ausnahme.
Weitere verwandte Ansätze
- Rise of the Tomb Raider erhielt explizite Direct3D-12-Multi-GPU-Unterstützung, vor allem für klassische Kombinationen ähnlicher GPUs.
- NVIDIA VR SLI wies bei Virtual Reality jeder GPU ein Auge zu. Das verteilt unabhängige Ansichten, bleibt jedoch NVIDIA-spezifisch.
- Unreal nDisplay kann Viewports oder Frustums auf weitere GPUs beziehungsweise Prozesse auslagern und das fertige Bild zur Ausgabe-GPU zurückführen. Der Einsatzbereich ist hauptsächlich Virtual Production.
- Forschung und High-Performance Computing verwenden seit Jahren Scheduler für heterogene GPUs, allerdings überwiegend für Compute- und Datenverarbeitungsaufgaben statt für einen allgemeinen UE5-Echtzeitrenderer.
Abgrenzung dieses Projekts
Das Projekt erfindet Direct3D-12-Explicit-Multiadapter nicht neu. Sein eigener Ansatz liegt in der Kombination aus tiefer UE5.8-RHI-Integration, UE-kompilierten Shadern auf unabhängigen Worker-Devices, frei wählbaren Spiel- und Compute-Workloads, Capability- und Performance-Scheduler, mehreren Fallback-Pfaden sowie dem langfristigen Ziel beliebiger Hersteller und mehrerer Worker.
Vergleich mit anderen Multi-GPU-Verfahren
| Verfahren | Arbeitsweise | Verhältnis zu diesem Projekt |
|---|---|---|
| SLI / CrossFire | Treiber- beziehungsweise Verbundlösung für meist ähnliche GPUs; häufig wird die Bildarbeit verteilt. | Dieses Projekt benötigt keinen herstellerspezifischen GPU-Verbund und weist Aufgaben ausdrücklich selbst zu. |
| AFR (Alternate Frame Rendering) | GPU 1 rendert einen Frame, GPU 2 den nächsten. | Das Projekt verteilt unabhängige Aufgaben statt aufeinanderfolgender Frames. Dadurch werden Frame-Abhängigkeiten und typisches AFR-Pacing vermieden. |
| SFR (Split Frame Rendering) | Mehrere GPUs bearbeiten Bereiche desselben Frames. | Erfordert enge Lastverteilung und viel Datenaustausch. Das Projekt bevorzugt vollständig abgrenzbare Workloads und überträgt deren Ergebnis. |
| Direct3D 12 Linked Multiadapter | Mehrere vom Treiber verbundene GPUs erscheinen als Knoten eines logischen Adapters. | Eignet sich eher für eng kompatible GPUs. Das Projekt verwendet unabhängige Devices (Unlinked/Explicit Multiadapter) und unterstützt dadurch heterogene Herstellerkombinationen. |
| Direct3D 12 Unlinked / Explicit Multiadapter | Die Anwendung verwaltet getrennte Adapter, Ressourcen und Synchronisation selbst. | Das ist die technische Familie des Projekts. Hinzu kommen die eigene UE5-RHI-Integration, Workload-Ziele, Fallbacks und der geplante Scheduler. |
| UE nDisplay mGPU / Multi-Process | Separate GPUs rendern bestimmte Viewports oder Frustums, vor allem für Virtual Production; Ergebnisse werden zur Ausgabe-GPU kopiert. | Ähnliche Idee der aufgabenweisen Trennung, aber für einen anderen Einsatzbereich. Dieses Projekt zielt auf allgemeine Spiel- und Compute-Workloads innerhalb der Engine. |
| Vulkan Device Groups | Ähnliche physische GPUs können ein gemeinsames logisches Device bilden. | Für beliebige heterogene GPUs meist ungeeignet, da Geräte einer Gruppe nahezu gleiche Eigenschaften besitzen müssen. Eine spätere Vulkan-Portierung benötigt daher wahrscheinlich getrennte Devices und External Memory/Semaphores. |
SLI und CrossFire bezeichnen den Verbund; AFR und SFR beschreiben mögliche Verteilungsmethoden innerhalb solcher Systeme. Sie sind deshalb nicht vollständig getrennte Kategorien.
Vorteile der geplanten Variante
- Herstellerunabhängig: NVIDIA, AMD und Intel können grundsätzlich kombiniert werden.
- Vorhandene Hardware nutzen: Auch eine sonst wenig genutzte integrierte GPU kann geeignete Nebenaufgaben übernehmen.
- Aufgaben statt Frames verteilen: Spiegel, Minimap oder Compute können mit eigener Auflösung und Aktualisierungsrate laufen.
- Keine identischen GPUs erforderlich: Unterschiedliche Fähigkeiten können gezielt genutzt werden.
- Kontrollierter Datenaustausch: Nur das benötigte Ergebnis muss zurück zur Primary-GPU.
- Robuste Fallback-Idee: Wenn direkte Texturen nicht gemeinsam nutzbar sind, bleibt der Shared-Buffer-Pfad.
- Erweiterbar: Scheduler, manuelles Mapping und mehrere Worker sind als spätere Stufen vorgesehen.
Nachteile und technische Risiken
- Hoher Entwicklungsaufwand: Geräte, Ressourcen, Zustände, Fences, Fehlerfälle und UE-Lebenszyklen müssen selbst verwaltet werden.
- Transfer kann den Gewinn aufzehren: Cross-Adapter Heaps liegen laut D3D12 nicht automatisch im schnellen lokalen VRAM. Bandbreite und Latenz müssen für jeden Workload gemessen werden.
- VRAM wird nicht einfach addiert: Benötigte Ressourcen können auf mehreren GPUs vorliegen und zusätzlichen Speicher verbrauchen.
- Langsame Worker können bremsen: Eine Aufgabe lohnt sich nur, wenn Rechengewinn größer als Übergabe-, Warte- und Kopierkosten ist.
- Nicht jeder Workload ist unabhängig: Hauptansicht, Lumen, Nanite und stark gekoppelte Renderpässe besitzen viele Abhängigkeiten und sind schwieriger auszulagern.
- Hardwareunterschiede: Formate, Shader-Funktionen, Queue-Fähigkeiten und Cross-Adapter-Support müssen pro GPU geprüft werden.
- Wartungsrisiko: Eingriffe in private D3D12RHI-Dateien können bei Engine-Updates angepasst werden müssen.
- Produktionsreife fehlt noch: Ressourcenpools, Timeouts, Device-Lost-Wiederherstellung, Scheduler und echte Szenentests sind offen.
Wann die Variante sinnvoll ist
Am aussichtsreichsten sind Aufgaben mit viel eigener Rechenarbeit, einem relativ kleinen Endergebnis und wenigen Abhängigkeiten zur Hauptszene. Gute frühe Kandidaten sind unabhängige Compute-Aufgaben sowie Scene Captures, die nicht in jedem Frame aktualisiert werden müssen. Weniger geeignet sind winzige Aufgaben mit großem Transfer oder Renderpässe, die ständig auf Ressourcen der Primary-GPU zugreifen.
Die entscheidende Regel für den späteren Scheduler lautet daher:
Worker-Gewinn > Vorbereitung + Datentransfer + Synchronisation + Rückintegration
Bekannte Grenzen des Proof of Concepts
- Die sichtbare Normal-/SafeCopy-/Ready-Kette bleibt an Unreals Submission- und Frame-System gekoppelt. Für sichtbare Outputs ist das akzeptabel; hochfrequente Hintergrundjobs benötigen weitere Architekturtests.
- Pro Worker existiert absichtlich nur ein persistenter sichtbarer Runtime-Slot. Mehrere parallele In-Flight-Jobs und Ringbuffer fehlen noch.
- Praktisch getestet ist Primary plus ein Worker. Mehrere Worker sind architektonisch vorgesehen, aber noch nicht praktisch validiert.
- World- und SceneCapture-Pfade verarbeiten pro Snapshot höchstens acht sichtbare
UStaticMeshComponents. - Der Static-Mesh-Pfad verwendet LOD0-Positionen, Normalen und Indices ohne UE-Materialien, Texturen, Beleuchtung, Schatten, Nanite, Lumen oder Culling. Für Cooked/Shipping wird ein eigener Geometrie-Cache oder Build-Schritt benötigt.
USkeletalMeshist bislang ein LOD0-ReferencePose-Proof. Bone-Skinning, Animation, Morph Targets und Cloth fehlen.- Die Workload-Registry sammelt Metadaten und Messwerte, führt aber noch keine automatische Zuweisung, Auflösungsänderung oder Ratenanpassung aus.
- Worker GPU Timing V1 misst nur den Rasterabschnitt. Cross-Adapter-Transfer und Primary-SafeCopy fehlen noch im Kostenmodell.
- Lifecycle-Raten können im Editor durch Fensterfokus und Background-Throttling verfälscht werden. Für Kostenentscheidungen sind echte GPU-Timestamps und geglättete Mehrfachmessungen maßgeblich.
- QueueProbe und private native Queues bleiben Diagnosepfade; Experimental/Direct ist noch nicht implementiert.
- Raw-D3D12-Shared-Ressourcen sind noch nicht vollständig in Unreals Residency-Management eingebunden. Device Lost, Timeouts, Speicherdruck und die vollständige Fallback-Matrix müssen vor produktiver Nutzung gehärtet werden.
- Linux/Vulkan mit getrennten Devices, External Memory und externer Synchronisation ist noch nicht implementiert.
Roadmap
- Cross-Adapter Transfer Timing V1: Worker-ColorTarget → Shared Buffer separat messen.
- Primary SafeCopy Timing ergänzen und die Integrationskosten getrennt halten.
- Kostenmodell V1 aus Worker-Raster, Transfer und Primary-Integration pro Workload ableiten.
- Weitere echte Workload-Klassen wie CCTV, Spiegel, Minimap und Compute anbinden; später Post Process oder neuronale Aufgaben untersuchen.
- D3D12-Pfade mit Intel Arc A750/A380 und RX 6900 XT in unterschiedlichen Primary-/Worker-Rollen gegenprüfen.
- Scheduler und Autotuner auf GPU-Eignung und gemessene Nettokosten statt Auslastungsprozent aufbauen.
- Material-, Textur- und Culling-Pfade für realistischere Worker-Szenen ergänzen.
- Skeletal Skinning mit Bone-Matrizen und Skin Weights erst bei einem echten animierten Worker-Workload ausbauen.
- Einen softwareseitigen Multi-Tier Resource Manager für Primary-VRAM, Worker-VRAM, RAM und später Storage untersuchen.
- Experimental-/Direct-Fast-Path nur mit State-Tracking, Residency, Hazards und sicherem Fallback entwickeln.
- Linux/Vulkan mit getrennten Devices, External Memory und externen Semaphoren/Fences untersuchen.
Git-Checkpoint
Der aktuelle Engine-Checkpoint 37c6d02 trägt die Nachricht Extend worker capture and workload instrumentation und folgt auf 45d5c27 (Add live SceneCapture worker rendering pipeline). Er umfasst acht Dateien mit 877 Einfügungen und 26 Löschungen. Enthalten sind der seit dem vorherigen Checkpoint weiterentwickelte Engine-Stand mit dynamischer SceneCapture-Auflösung, Scheduler-/Polling-Korrektur, SkeletalMesh-ReferencePose, generischer Workload-Registry, Runtime Metrics V1 und Worker GPU Timing V1. Ein Push wurde in diesem Dokumentationsschritt nicht ausgeführt.
Quellen
- Interner Entwicklungsstand und verifizierte Testprotokolle vom 14. September 2026; Commit
37c6d02auf Branchmgpu-experimental. Neu belegt sind Workload-Registry, Runtime Metrics V1 und native D3D12-Timestamp-Messungen des Worker-Rasterabschnitts. - Microsoft: Direct3D 12 Multi-adapter systems
- Microsoft: Shared heaps
- Microsoft: Timing und Timestamp Queries in Direct3D 12
- Microsoft: D3D12 Heterogeneous Multiadapter Sample
- Microsoft: UE4 Elemental Demo mit heterogenem Multiadapter
- Intel: Multi-Adapter Support in DirectX 12
- Microsoft: Ashes of the Singularity und heterogene Adapter
- Epic: NVIDIA SLI Alternate Frame Rendering
- Epic: Multi-Process Rendering
- Khronos: Vulkan Specification – Device Groups
