UE5 Heterogeneous Multi-GPU
Hinweis: Projektstand: 10. September 2026. Engine-Checkpoint: 45d5c27 (Add live SceneCapture worker rendering pipeline) auf Branch mgpu-experimental; der Engine-Working-Tree war danach sauber, 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 A380 als zusätzlicher dGPU-Worker-Gegencheck vorgesehen |
| Git-Stand | Commit 45d5c27 auf Branch mgpu-experimental; Engine-Working-Tree danach sauber, nicht gepusht
|
| Windows/D3D12-Proof-of-Concept | Durchgängiger sichtbarer Worker-Rasterpfad von der laufenden UE-Welt über getaggte Static Meshes und eine echte SceneCapture2D-Kamera bis zur persistenten UE-Textur; Live-Stream 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.
Test- und Diagnosebefehle
| Befehl | Zweck |
|---|---|
ExperimentalMultiGPU.TestComputeShader
|
Führt den vollständigen Worker-Compute-Testpfad aus. Er enthält auch ältere Debug- und Regressionstests mit CPU-Readback und ist daher nicht mit dem reinen asynchronen Runtime-Pfad gleichzusetzen. |
ExperimentalMultiGPU.TestTextureConsumer
|
Testet TextureConsumerCS auf der Primary-GPU. Die aktuelle Ready-Textur wird über UE-RHI gelesen und anhand ausgewählter Testpixel per Debug-Readback geprüft.
|
ExperimentalMultiGPU.TestTextureBridge
|
Prüft die Bridge von der fertigen FTextureRHIRef zu UExperimentalMultiGPUTexture und damit den UE- und materialtauglichen Texturpfad.
|
ExperimentalMultiGPU.SpawnDisplayActor
|
Spawnt den Test-Actor mit Plane im Spiel. Das Material zeigt darauf die vom Worker erzeugte Textur an. |
ExperimentalMultiGPU.TestRasterWorker
|
Rasterisiert auf der Worker-GPU ein echtes RGB-Dreieck per Vertex- und Pixel-Shader und transportiert das Ergebnis über den normalen SafeCopy-Pfad bis zum DisplayActor. |
ExperimentalMultiGPU.StartRasterStream / ExperimentalMultiGPU.StopRasterStream
|
Startet beziehungsweise stoppt den kontinuierlichen RGB-Dreieck-RasterStream mit frei wählbarer Zielrate, optionalem Submit-Limit und Quiet-Modus. |
ExperimentalMultiGPU.TestRaster3DWorker
|
Rendert einmalig den perspektivischen 3D-Testwürfel mit Depth Buffer und echten Vertex-/Indexbuffern. |
ExperimentalMultiGPU.StartRaster3DStream / ExperimentalMultiGPU.StopRaster3DStream
|
Startet beziehungsweise stoppt den kontinuierlichen Raster3D-Test. |
ExperimentalMultiGPU.TestStaticMeshWorker [AssetPath]
|
Extrahiert und rendert LOD0-Geometrie eines UE-Static-Mesh-Assets; ohne optionalen Pfad wird das konfigurierte Testasset verwendet. |
ExperimentalMultiGPU.TestStaticMeshScene
|
Rendert Sphere, Cube und Cylinder gemeinsam mit drei Draw Calls und einem anschließenden Transfer. |
ExperimentalMultiGPU.TestWorldStaticMeshScene
|
Rendert einen einmaligen POD-Snapshot der sichtbaren, mit MultiGPUWorker getaggten StaticMeshComponents aus der laufenden World. Der sichtbare PIE-Nachweis ist erfolgreich.
|
ExperimentalMultiGPU.StartWorldStaticMeshSceneStream [Hz] [MaxSuccessfulSubmits] [quiet] / StopWorldStaticMeshSceneStream
|
Startet beziehungsweise stoppt den kontinuierlichen Stream der getaggten UE-Welt mit Live-Transforms und persistentem Mesh-Cache. |
ExperimentalMultiGPU.TestSceneCaptureStaticMeshScene
|
Rendert einen einmaligen Worker-Snapshot aus Sicht der mit MultiGPUWorkerCamera markierten SceneCapture2D-Kamera.
|
ExperimentalMultiGPU.StartSceneCaptureStaticMeshSceneStream [Hz] [MaxSuccessfulSubmits] [quiet] / StopSceneCaptureStaticMeshSceneStream
|
Startet beziehungsweise stoppt den Live-Worker-Kanal mit fortlaufend aktualisierten Kamera-, FOV- und Actor-Daten. |
ExperimentalMultiGPU.StartTextureStream
|
Startet den sichtbaren Normal-Pfad: Worker-Textur → Cross-Adapter-Transfer → SafeCopy → UE-Textur → Material. |
ExperimentalMultiGPU.StopTextureStream
|
Verhindert weitere Submits des laufenden Streams. Ein bereits eingereichter GPU-Job darf noch sicher fertig werden. |
ExperimentalMultiGPU.StartQueueProbe
|
Startet den Diagnose-Benchmark für Primary-Queue-, Fence- und Submission-Verhalten. Der Probe verändert die sichtbare Textur nicht. |
Texture Stream
Grundsyntax:
ExperimentalMultiGPU.StartTextureStream [Hz] [MaxSuccessfulSubmits] [quiet]
| Beispiel | Wirkung |
|---|---|
ExperimentalMultiGPU.StartTextureStream
|
Standardmäßig etwa 5 Hz, ohne Submit-Limit. |
ExperimentalMultiGPU.StartTextureStream 37.5
|
Fordert 37,5 Hz ohne Submit-Limit an. Sowohl 37.5 als auch 37,5 werden akzeptiert.
|
ExperimentalMultiGPU.StartTextureStream 1000 100
|
Fordert 1000 Hz an und stoppt nach exakt 100 erfolgreichen Submits. |
ExperimentalMultiGPU.StartTextureStream 1000 100 quiet
|
Wie zuvor, unterdrückt aber Per-Job- und Busy-Logspam. |
Die Hz-Angabe ist eine Ziel- beziehungsweise Request-Frequenz. 1000 bedeutet daher nicht automatisch 1000 fertige Texturen pro Sekunde. Die tatsächliche Rate hängt unter anderem von Timer, Submission, Backpressure, Queue-Latenz und Slot-Lifecycle ab. MaxSuccessfulSubmits zählt nur angenommene Submits; Busy- und Failed-Versuche verbrauchen das Limit nicht.
QueueProbe
Grundsyntax:
ExperimentalMultiGPU.StartQueueProbe <Hz> <MaxSubmits> [quiet] [Mode] [Queue]
| Modus | Ausgeführter Pfad |
|---|---|
full
|
Worker Compute → Cross-Adapter-Transfer → Primary Wait → Copy → Completion Fence. |
waitonly
|
Worker Compute → Cross-Adapter-Transfer → Primary wartet auf WorkerReadyFence → Completion Fence. Es findet keine Texturkopie statt. |
signalonly
|
Keine Textur, kein Worker-Wait und keine Kopie; nur Queue und Fence. Dieser Modus isoliert das Queue-Verhalten. |
commandonly
|
Reicht eine leere echte Worker-Command-List ein und trennt Command-Submission von GPU-Arbeit. |
barrieronly / barrierprivate
|
Isoliert Resource-Barriers auf Worker-Ressourcen beziehungsweise einer privaten Testtextur. |
copybuffer
|
Führt eine minimale echte CopyBufferRegion-Operation über 256 Byte aus.
|
| Queue | Bedeutung |
|---|---|
graphics
|
Vorhandene UE Primary Graphics-/Direct-Queue über RHIRunOnQueue.
|
copy
|
Vorhandene UE Primary Copy Queue über RHIRunOnQueue.
|
native
|
Vollständig private native D3D12-DIRECT-Queue auf der Primary; reine Diagnose, kein fertiger Experimental-Modus. |
worker
|
Vorhandene native Worker-DIRECT-Queue. |
worker-copy
|
Separate native Worker-COPY-Queue. |
worker-high
|
Separate Worker-DIRECT-Queue mit hoher Priorität. |
Typische Beispiele:
ExperimentalMultiGPU.StartQueueProbe 1000 100 quiet full graphics ExperimentalMultiGPU.StartQueueProbe 1000 100 quiet waitonly graphics ExperimentalMultiGPU.StartQueueProbe 1000 100 quiet signalonly graphics ExperimentalMultiGPU.StartQueueProbe 1000 100 quiet signalonly copy ExperimentalMultiGPU.StartQueueProbe 1000 100 quiet signalonly native
Der letzte Befehl ist aktuell die wichtigste isolierte Gegenprobe. Mit der privaten nativen Queue wurde bei 30 FPS eine durchschnittliche Latenz primaryExecuteToReadyAvgMs = 0.014 ms gemessen.
Benchmark-CVars
| CVar | Zweck |
|---|---|
r.VSync 0
|
Deaktiviert VSync, damit es die Messung nicht begrenzt. |
t.MaxFPS 30t.MaxFPS 60t.MaxFPS 120
|
Setzt die Engine-Framerate für reproduzierbare Frame-Coupling-Tests. |
t.MaxFPS 0
|
Entfernt das normale t.MaxFPS-Limit. Dieser Lauf ist als zusätzliche Diagnose-Gegenprobe vorgesehen.
|
Aktueller nächster Schritt
Der Live-SceneCapture-Kanal ist funktional nachgewiesen. Als nächstes soll die feste 128×96-Ausgabe durch eine konfigurierbare Capture-Auflösung ersetzt werden. Danach folgt ein erster echter Nutzlast-Proof als CCTV, Spiegel oder Minimap. Parallel bleiben mehrere Mesh-Sections, Material-/Textur-Bindings und späteres Culling offen. QueueProbe bleibt ein Diagnosewerkzeug; Experimental/Direct, Nanite, Lumen und automatische Workload-Verteilung sind spätere eigenständige Ausbaustufen.
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
- Der asynchrone 128×96-End-to-End-Pfad ist über Dimensionen, Dispatch, Transfer, Fences, Pending/Ready und Reuse verifiziert; ein eigener 128×96-Pixel-Readback fehlt noch. Der synchrone 64×64-Debugtest prüft weiterhin alle 4096 Pixel.
- Der sichtbare Worker-Output ist derzeit auf 128×96 Pixel in
DXGI_FORMAT_R8G8B8A8_UNORMmit Sample Count 1 begrenzt. Konfigurierbare Auflösung, weitere Formate, Mips, Arrays, Slices und MSAA sind offen. - Pro Worker existiert absichtlich nur ein persistenter Runtime-Slot. Mehrere parallele In-Flight-Jobs sowie Double-, Triple- oder Ring-Buffering fehlen noch.
- Die Primary-/SafeCopy-/Ready-Kette bleibt trotz zusätzlichem non-blocking
DispatchToRHIThreadeffektiv an den Frame-/Submission-Zyklus gekoppelt. - Echte Intel-Worker-GPU-Arbeit skaliert im aktuellen UE-Prozess mit 30/60/120 FPS. Der Standalone-Test widerlegt eine allgemeine Intel-/WDDM-/D3D12-Grenze; die genaue UE-spezifische Ursache bleibt offen.
- QueueProbe ist reine Diagnose. Private native Queues sind noch kein Experimental-/Direct-Modus und beschreiben keine UE-eigene sichtbare Textur.
- Es gibt noch keine Umschaltung zwischen Normal und Experimental sowie keinen automatischen Session-Fallback.
- Raw-D3D12-Shared-Ressourcen sind noch nicht vollständig in Unreals Residency-Management eingebunden.
- Praktisch getestet ist Primary plus ein Worker. Die Architektur ist auf mehrere Worker ausgelegt, N-GPU ist aber noch nicht praktisch validiert.
- Raster3D, einzelne UE-Static-Meshes, die Drei-Mesh-Szene sowie WorldStaticMeshScene sind sichtbar nachgewiesen. World- und SceneCapture-Pfade besitzen inzwischen laufende Streams; pro Snapshot werden weiterhin höchstens acht sichtbare StaticMeshComponents verarbeitet.
- Der Worker übernimmt weiterhin nur LOD0-POSITION/NORMAL-Daten und Indices. Materialien, Texturen, Mesh-Sections, Beleuchtung, Schatten, Skeletal Meshes, Nanite, Lumen sowie Frustum-/Occlusion-Culling fehlen. Für Cooked/Shipping muss der Zugriff auf benötigte Geometrie über einen eigenen Cache oder Build-Schritt abgesichert werden.
- Ein explizites Read-vs-Write-Hazard-Modell für die persistente Primary-Textur fehlt noch und muss vor mehreren Slots oder einem nativen Experimental-Texturpfad gelöst werden.
- Die projektseitigen UpscalerTest-Dateien mit DisplayActor und Texture Stream liegen außerhalb des Engine-Git-Repositories und sind nicht Bestandteil von Commit
0719998. - 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
- Worker-Target und Transport auf frei wählbare SceneCapture-Auflösungen erweitern.
- CCTV, Spiegel oder Minimap als ersten realen, unabhängigen Worker-Workload umsetzen.
- Mehrere Mesh-Sections, Material-/Textur-Bindings und später Culling ergänzen, ohne die POD-/Worker-Trennung aufzugeben.
- D3D12-Adapter-, Raster- und SceneCapture-Pfade auf der Intel Arc A380 und später mit RX 6900 XT als Primary gegenprüfen.
- Microbenchmarks und reale Workloads zu einem Benchmark-/Autotuner ausbauen, der den Netto-Nutzen pro GPU-Paar bewertet.
- Nach Portierung des eigentlichen Projekts schwere UE-Demos wie Electric Dreams und später Lumen in the Land of Nanite als Stresstest verwenden.
- Einen Multi-Tier Resource Manager für Primary-VRAM, Worker-VRAM und RAM mit Cache, Prefetch und Duplizierung untersuchen; dies ist keine transparente gemeinsame Hardware-VRAM-Lösung.
- Experimental-/Direct nur mit State-Tracking, Residency, Queue-Reihenfolge, Capability-Selbsttest und Session-Fallback auf Normal entwickeln.
- Weitere Runtime-Slots oder Ringbuffer erst bei echtem Bedarf und mit gelöstem Generation-, Fence-, Reuse- und Consumer-Hazard-Modell ergänzen.
- Capability-Matrix, Scheduler, manuelle Overrides und mehrere Worker-GPUs entwickeln.
- Linux/Vulkan mit getrennten Devices, External Memory und externer Synchronisation untersuchen.
Git-Checkpoint
Der Engine-Checkpoint 45d5c27 mit der Nachricht Add live SceneCapture worker rendering pipeline folgt auf 0719998. Er umfasst 13 Dateien mit 4443 Einfügungen und 11 Löschungen. Enthalten sind Raster3D, StaticMesh-, Multi-Mesh-, WorldScene- und Live-SceneCapture-Enginepfade. Die projektseitige Datei UpscalerTest/Private/ExperimentalMultiGPUDisplayActor.cpp liegt außerhalb des Engine-Repositories und ist daher nicht Teil des Commits. Binaries, Intermediate-, Cache-, Saved- und Log-Dateien wurden nicht aufgenommen. Der Engine-Working-Tree war nach dem Commit sauber; ein Push erfolgte nicht.
Quellen
- Interner Entwicklungsstand und verifizierte Testprotokolle vom 10. September 2026; Commit
45d5c27. WorldStaticMeshScene, WorldStaticMeshSceneStream und der SceneCaptureStaticMeshSceneStream sind sichtbar im PIE-Level getestet. - Microsoft: Direct3D 12 Multi-adapter systems
- Microsoft: Shared heaps
- 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
