Kurze Rückmeldung mit meiner frischen Erfahrung:
Umgebung
Direct3D 9Ex auf Windows 10 (eigentlich Windows Server 2022) mit steinalter Nvidia-Gurke.
Konzept
Textur-Streaming im laufenden Betrieb aus separaten Threads. Die Daten dafür kommen direkt aus einer Memory Mapped File.
D3DCREATE_MULTITHREADED soll furchtbare Performance haben weil es einfach ein globaler Mutex ist (sagt jedenfalls D3D-Lead Chuck Walbourn
hier in einem Kommentar. Original-Antwort stammt lustigerweise von Aramis – kleine Welt!). Deshalb verzichte ich drauf und mache meinen eigenen Blackjack-und-Nutten-Mutex, den sich der Render-Thread zwischen
BeginScene() und
EndScene() schnappt. Das Textur-Streaming muss warten, bis der frei ist.
Im Streaming-Thread:
CreateOffScreenPlainSurface(), per
LockRect() füllen.
CreateTexture() und
UpdateSurface(), um das CPU-Surface auf die GPU zu kopieren. (Direktes Lock/Unlock der Textur ist ja in D3D9Ex unmöglich, da es keinen
D3DPOOL_MANAGED mehr gibt. D3D9Ex führt direkte Initialisierung bei
CreateTexture() ein, aber die funktioniert nur auf Texturen ohne Mip Maps.)
Selbstverständlich habe ich nur die D3D-Aufrufe selber via Mutex synchronisiert; das Laden der Datei usw. geschieht außerhalb des Mutex.
Fehlgeschlagener Versuch
Die ersten Ergebnisse waren brutal: Bis zu 1500 ms für eine 4k-Textur. Dann fiel mir auf, dass die meiste Zeit für Race Conditions draufging – ich habe den Treiber wohl geflutet (oder meinen Prozess mit Page Faults auf die Memory Mapped Files lahmgelegt).
Ergebnis
Habe auf ein Producer-Consumer-Pattern umgestellt, so dass nicht mehr als eine Textur gleichzeitig gestreamt wird. Performance verzehnfachte sich. (Dafür habe ich aber nur noch ein Zehntel der Nebenläufigkeit.) Der Upload einer 4k-Textur geschieht nun regelmäßig in unter 150 ms. Das ist ein merklicher Ruckler,
aber:
Von 150 ms gehen ungefähr 100 für das Kopieren der Textur-Bytes aus der Memory Mapped File in den Surface Memory drauf. Ich nutze bereits
PrefetchVirtualMemory(), um Hard Page Faults zu vermeiden. Trotzdem verdoppelt sich die Kopier-Performance fast, wenn ich vor dem Upload noch schnell alle Pages ins Working Set zwinge, indem ich bspw. die Texel in 4096-Byte-Abständen aufsummiere. Ich habe die D3D-Calls gemessen – ab und zu hat ein
UnlockRect() zwei Millisekunden gebraucht, oder ein
Release() acht. Die sind also definitiv alle asynchron umgesetzt und lassen keine genaue Performance-Analyse zu.
Für mich ist eine 4k-Textur in 150 ms Upload-Unterbrechnung erstmal genug. Das ist ziemlich nah an deinen
“120 ms für 6k²”, aber mein Rechner ist steinalt, also schätze ich, dass es nicht besser geht. (Meine tatsächliche Latenz ist viel höher, da ich vor dem Upload ja noch die Datei mappe, die Texel ins Working Set zwinge, und das Producer-Consumer-Pattern alles in eine Warteschlange zwingt.) Für besseres Multi-Threading muss ich halt auf eine neuere Grafik-API.
Moral von meiner Geschichte: Mein Locking hat viel mehr ausgemacht als die D3D-Aufrufe. Jetzt macht noch das Prefetching der Texel mehr aus.