Wie lade ich grafische Ressourcen asynchron?

9

Denken wir plattformunabhängig: Ich möchte einige grafische Ressourcen laden, während der Rest des Spiels läuft.

Im Prinzip kann ich die eigentlichen Dateien in einen separaten Thread laden oder asynchrone E / A verwenden. Aber bei grafischen Objekten muss ich sie auf die GPU hochladen, und das kann (normalerweise) nur im Haupt-Thread durchgeführt werden.

Ich kann meine Spielschleife so ändern, dass sie ungefähr so ​​aussieht:

while true do
    update()
    for each pending resource do
        load resource to gpu
    end
    draw()
end

während ein separater Thread Ressourcen von der Festplatte in den RAM laden.

Wenn jedoch viele große Ressourcen geladen werden müssen, kann dies dazu führen, dass ich eine Frame-Frist verpasse und schließlich Frames fallen lasse. Also kann ich die Schleife folgendermaßen ändern:

while true do
    update()
    if there are pending resources then
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

Effektiv nur eine Ressource pro Frame laden. Wenn jedoch viele kleine Ressourcen geladen werden müssen, dauert das Laden aller Ressourcen viele Frames und es wird viel Zeit verschwendet.

Optimalerweise möchte ich mein Laden folgendermaßen zeitlich festlegen:

while true do
    time_start = get_time()
    update()
    while there are pending resources then
        current_time = get_time()
        if (current_time - time_start) + time_to_load(resource) >= 1/60 then
            break
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

Auf diese Weise würde ich eine Ressource nur laden, wenn ich dies innerhalb der Zeit tun kann, die ich für diesen Frame habe. Leider erfordert dies eine Möglichkeit, die Zeit abzuschätzen, die zum Laden einer bestimmten Ressource benötigt wird, und meines Wissens gibt es normalerweise keine Möglichkeiten, dies zu tun.

Was fehlt mir hier? Wie viele Spiele können all ihre Inhalte vollständig asynchron und ohne ausgelassene Frames oder extrem lange Ladezeiten laden?

Panda Pyjama
quelle

Antworten:

7

Beginnen wir mit der Annahme einer perfekten Welt. Das Laden einer Ressource erfolgt in zwei Schritten: Erstens erhalten Sie sie im richtigen Format von Ihrem Speichermedium in den Speicher und zweitens übertragen Sie sie über den Speicherbus in den Videospeicher. Keiner dieser beiden Schritte benötigt tatsächlich Zeit für Ihren Hauptthread - es muss nur beteiligt werden, um einen E / A-Befehl auszugeben. Sowohl Ihre CPU als auch Ihre GPU können andere Aufgaben ausführen, während die Ressource kopiert wird. Die einzige echte Ressource, die verbraucht wird, ist die Speicherbandbreite.

Wenn Sie eine Plattform ohne große Abstraktionsschicht zwischen Ihnen und der Hardware verwenden, macht die API diese Konzepte wahrscheinlich direkt verfügbar. Aber wenn Sie sich auf einem PC befinden, sitzt wahrscheinlich ein Treiber zwischen Ihnen und der GPU, und er möchte die Dinge auf seine Weise erledigen. Abhängig von der API können Sie möglicherweise eine Textur erstellen, die von Ihrem eigenen Speicher unterstützt wird. Wenn Sie jedoch eher die API "Textur erstellen" aufrufen, wird die Textur in einen Speicher kopiert, den der Treiber besitzt. In diesem Fall hat das Erstellen einer Textur einen festen Overhead und eine Zeit, die proportional zur Größe der Textur ist. Danach kann der Treiber alles tun - er überträgt die Textur möglicherweise proaktiv auf VRAM oder lädt die Textur erst hoch, wenn Sie versuchen, sie zum ersten Mal zu rendern.

Möglicherweise können Sie etwas dagegen tun oder nicht, aber Sie können die Zeit abschätzen, die für den Aufruf "Textur erstellen" erforderlich ist. Natürlich werden sich alle Zahlen je nach Hardware und Software ändern, daher lohnt es sich wahrscheinlich nicht, einige Zeit damit zu verbringen, sie rückzuentwickeln. Also probieren Sie es einfach aus und sehen Sie! Wählen Sie eine Metrik aus: entweder "Anzahl der Texturen pro Frame" oder "Gesamtgröße der Texturen pro Frame", wählen Sie ein Kontingent (z. B. 4 Texturen pro Frame) und beginnen Sie mit dem Stresstest.

In pathologischen Fällen müssen Sie möglicherweise sogar beide Kontingente gleichzeitig verfolgen (z. B. maximal 4 Texturen pro Frame oder 2 MB Texturen pro Frame, je nachdem, welcher Wert niedriger ist). Der eigentliche Trick bei den meisten Textur-Streams besteht jedoch darin, herauszufinden, welche Texturen in Ihren begrenzten Speicher passen sollen, und nicht, wie lange es dauert, sie zu kopieren.

Auch pathologische Fälle für die Texturerstellung - wie viele winzige Texturen, die gleichzeitig benötigt werden - sind in der Regel auch für andere Bereiche pathologische Fälle. Es lohnt sich, eine einfache funktionierende Implementierung zu erhalten, bevor Sie sich Gedanken darüber machen, wie viele Mikrosekunden eine Textur zum Kopieren benötigt. (Außerdem tritt der tatsächliche Leistungseinbruch möglicherweise nicht als CPU-Zeit beim Aufruf "Textur erstellen" auf, sondern als GPU-Zeit beim ersten Frame, bei dem Sie die Textur verwenden.)

John Calsbeek
quelle
Das ist eine ziemlich gute Erklärung. Viele Dinge, die ich nicht wusste, aber die viel Sinn ergeben. Anstatt es einem Stresstest zu unterziehen, würde ich den Aufwand für die Texturerstellung zur Laufzeit messen, sanft starten und bis zu 80% der verfügbaren Ausführungszeit drosseln, um Platz für Ausreißer zu lassen.
Panda Pyjama
@PandaPajama Ich bin ein bisschen skeptisch. Ich würde erwarten, dass der stationäre Zustand "keine zu kopierenden Texturen" und eine enorme Varianz ist. Und wie gesagt, ich vermute, dass ein Teil des Treffers der erste Render-Frame ist, der die Textur verwendet, die viel schwieriger dynamisch zu messen ist, ohne die Leistung zu beeinträchtigen.
John Calsbeek
Hier ist auch eine NVIDIA-Präsentation zu asynchronen Texturübertragungen. Das Wichtigste, was es nach Hause fährt, ist, dass die Verwendung einer Textur zu früh nach dem Hochladen zum Stillstand kommt. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
John Calsbeek
Ich bin kein Fahrer-Dev-Jockey, aber ist das üblich? Es ist nicht sehr sinnvoll, Treiber auf diese Weise zu implementieren, da Textur-Erstverwendungen sehr wahrscheinlich in Spitzen (wie am Anfang jedes Levels) statt in Abständen entlang der Zeitachse auftreten.
Panda Pyjama
@PandaPajama Es ist auch üblich, dass Anwendungen mehr Texturen erstellen, als VRAM verfügbar ist, und Texturen erstellen und diese dann nie verwenden. Ein häufiger Fall ist "Erstellen Sie eine Reihe von Texturen und zeichnen Sie dann sofort eine Szene, in der sie verwendet werden". In diesem Fall hilft es dem Fahrer, faul zu sein, da er herausfinden kann, welche Texturen tatsächlich verwendet werden, und dieser erste Frame wird trotzdem hängen bleiben . Aber ich bin auch kein Fahrerentwickler, nimm es mit einem Körnchen Salz (und teste es!).
John Calsbeek