Optimierung des Renderns einer Szene aus dem Disney-Cartoon "Moana". Teil 1

Die Walt Disney Animation Studios (WDAS) haben der Rendering-Forschungsgemeinschaft kürzlich ein unschätzbares Geschenk gemacht, indem sie eine vollständige Inselszene aus dem Moana- Cartoon veröffentlicht haben . Geometrie und Texturen für einen Frame belegen mehr als 70 GB Speicherplatz. Dies ist ein hervorragendes Beispiel für die Komplexität, mit der Rendering-Systeme heute umgehen müssen. Noch nie konnten Forscher und Entwickler, die außerhalb von Filmstudios am Rendern beteiligt waren, mit so realistischen Szenen arbeiten.

So sieht das Ergebnis des Renderns einer Szene mit modernem pbrt aus:


Eine Insel aus Moana, die von pbrt-v3 mit einer Auflösung von 2048 x 858 mit 256 Abtastwerten pro Pixel gerendert wurde. Die gesamte Renderzeit auf einer 12-Core / 24-Thread-Instanz von Google Compute Engine mit einer Frequenz von 2 GHz mit der neuesten Version von pbrt-v3 betrug 1 h 44 min 45 s.

Seitens Disney war es eine große Aufgabe, sie musste die Szene aus ihrem eigenen internen Format extrahieren und in das übliche konvertieren; Besonderer Dank geht an sie für die Zeit, die sie für das Verpacken und Aufbereiten dieser Daten für die breite Verwendung aufgewendet hat. Ich bin sicher, dass ihre Arbeit in Zukunft gut belohnt wird, da Forscher diese Szene verwenden, um die Probleme beim effizienten Rendern von Szenen dieser Komplexität zu untersuchen.

Diese Szene hat mir bereits viel beigebracht und mir ermöglicht, den pbrt-Renderer zu verbessern, aber bevor wir darauf eingehen, werde ich eine kurze Geschichte erzählen, um den Kontext zu verstehen.

Der Hash war das nicht


Während eines Praktikums im Pixar-Rendering-Team habe ich vor vielen Jahren eine merkwürdige Lektion gelernt: „Interessante“ Dinge erscheinen fast immer, wenn Eingabedaten an das Programmsystem übergeben werden, die sich erheblich von allem unterscheiden, was vorher war. Selbst in gut geschriebenen und ausgereiften Softwaresystemen führen neue Arten von Eingaben fast immer zur Entdeckung unbekannter Fehler in einer vorhandenen Implementierung.

Diese Lektion habe ich zum ersten Mal während der Produktion von Toy Story 2 gelernt. Eines Tages bemerkte jemand, dass erstaunlich viel Zeit damit verbracht wurde, RIB-Szenenbeschreibungsdateien zu analysieren. Jemand anderes vom Rendering-Team (ich denke, es war Craig Kolb) hat den Profiler gestartet und angefangen, es herauszufinden.

Es stellte sich heraus, dass der größte Teil der Analysezeit mit Suchen in der Hash-Tabelle belegt war, die für das Internieren von Zeichenfolgen verwendet wurde . Die Hash-Tabelle war ziemlich klein, wahrscheinlich 256 Elemente, und als mehrere Werte in eine Zelle gehasht wurden, organisierte sie eine Kette. Nach der ersten Implementierung der Hash-Tabelle verging viel Zeit und es befanden sich nun Zehntausende von Objekten in den Szenen, sodass eine so kleine Tabelle schnell gefüllt und unwirksam wurde.

Es war am ratsamsten, die Größe des Tisches einfach zu vergrößern - all dies geschah auf der Höhe des Workflows, sodass keine Zeit für eine elegante Lösung blieb, z. B. die Größe des Tisches beim Ausfüllen zu vergrößern. Wir nehmen eine Änderung in einer Zeile vor, erstellen die Anwendung neu, führen vor dem Festschreiben einen Schnelltest durch und ... es treten keine Geschwindigkeitsverbesserungen auf. Das Durchsuchen einer Hash-Tabelle dauert genauso lange. Super!

Nach weiteren Untersuchungen stellten wir fest, dass die verwendete Hash-Tabellenfunktion der folgenden ähnlich war:

int hash(const char *str) { return str[0]; } 

(Verzeihen Sie mir, Pixar, wenn ich Ihren streng geheimen RenderMan-Quellcode enthüllt habe.)

Die "Hash" -Funktion wurde bereits in den 1980er Jahren implementiert. Zu diesem Zeitpunkt war der Programmierer wahrscheinlich der Ansicht, dass der Rechenaufwand für die Überprüfung der Auswirkung aller Zeichen in der Zeichenfolge auf den Hashwert zu hoch und nicht wert wäre. (Ich denke, wenn es nur wenige Objekte und 256 Elemente in der Hash-Tabelle in der Szene gab, war das genug.)

Eine weitere veraltete Implementierung trug dazu bei: Von dem Moment an, als Pixar mit der Erstellung seiner Filme begann, sind die Namen der Objekte in den Szenen erheblich gewachsen, z. B. „BuzzLightyear / LeftArm / Hand / IndexFinger / Knuckle2“. In einem Anfangsstadium der Pipeline wurde jedoch ein Puffer mit fester Länge verwendet, um die Namen von Objekten zu speichern, und alle langen Namen wurden gekürzt, wobei nur das Ende beibehalten wurde. Mit etwas Glück wurde am Anfang eine Ellipse hinzugefügt, um deutlich zu machen, dass ein Teil des Namens verloren gegangen ist: "... year / LeftArm / Hand / IndexFinger / Knuckle2 ".

Anschließend hatten alle Namen der Objekte, die der Renderer sah, diese Form, die Hash-Funktion hat sie alle als "." In einen Speicher gehasht, und die Hash-Tabelle war tatsächlich eine große verknüpfte Liste. Gute alte Zeiten. Zumindest nachdem wir es herausgefunden hatten, haben wir diesen Fehler schnell behoben.

Faszinierende Innovation


Diese Lektion wurde mir letztes Jahr in Erinnerung gerufen, als Heather Pritchet und Rasmus Tamstorf von WDAS mich kontaktierten und fragten, ob ich daran interessiert wäre, die mögliche Qualität des Renderns der Szene von Moana in pbrt 1 zu überprüfen . Natürlich stimmte ich zu. Ich war glücklich zu helfen und habe mich gefragt, wie alles ausgehen wird.

Der naive Optimist in mir hoffte, dass es keine großen Überraschungen geben würde - am Ende wurde die erste Version von pbrt vor ungefähr 15 Jahren veröffentlicht, und viele Leute verwendeten und studierten ihren Code viele Jahre lang. Sie können sicher sein, dass es keine Interferenzen wie bei der alten Hash-Funktion von RenderMan gibt, oder?

Die Antwort war natürlich nein. (Und deshalb schreibe ich diesen und einige andere Beiträge.) Obwohl ich ein wenig enttäuscht war, dass pbrt nicht „out of the box“ perfekt war, denke ich, dass meine Erfahrung mit der Moana- Szene die erste Bestätigung für den Wert der Veröffentlichung dieser Szene war ;; pbrt ist bereits zu einem besseren System geworden, da ich herausgefunden habe, wie ich mit dieser Szene umgehen soll.

Erste Renderings


Nachdem ich auf die Szene zugegriffen hatte, lud ich sie sofort herunter (es dauerte mehrere Stunden mit meiner Heim-Internetverbindung) und entpackte sie aus tar, wobei ich 29 GB pbrt-Dateien und 38 GB ptex 2- Texturkarten erhielt . Ich habe munter versucht, die Szene auf meinem Heimsystem zu rendern (mit 16 GB RAM und einer 4-Kern-CPU). Nachdem ich nach einiger Zeit zum Computer zurückgekehrt war, stellte ich fest, dass er eingefroren war, der gesamte Arbeitsspeicher voll war und pbrt immer noch versuchte, das Parsen der Szenenbeschreibung abzuschließen. Das Betriebssystem versuchte, die Aufgabe mithilfe des virtuellen Speichers zu bewältigen, aber es schien hoffnungslos. Nachdem ich den Prozess abgeschlossen hatte, musste ich noch eine Minute warten, bis das System auf meine Aktionen reagierte.

Der nächste Versuch war eine Instanz von Google Compute Engine, mit der Sie mehr RAM (120 GB) und mehr CPU (32 Threads auf 16 CPUs) verwenden können. Die gute Nachricht war, dass pbrt die Szene erfolgreich rendern konnte (dank der Arbeit von Heather und Rasmus, sie in das pbrt-Format zu konvertieren). Es war sehr aufregend zu sehen, dass pbrt relativ gute Pixel für qualitativ hochwertige Filminhalte erzeugen kann, aber die Geschwindigkeit erwies sich als nicht so erstaunlich: 34 min 58 s nur zum Parsen der Szenenbeschreibung und beim Rendern des Systems bis zu 70 GB RAM.

Ja, es gab 29 Gigabyte Szenenbeschreibungsdateien im pbrt-Format auf der Festplatte, die gespart werden mussten, sodass ich nicht damit gerechnet hatte, dass die erste Phase einige Sekunden dauern würde. Aber eine halbe Stunde verbringen, noch bevor die Strahlen zu verfolgen beginnen? Dies erschwert die Arbeit mit der Szene erheblich.

Auf der anderen Seite sagte uns diese Geschwindigkeit, dass im Code wahrscheinlich etwas sehr übelriechendes passiert; nicht nur "Matrixinversion kann 10% schneller durchgeführt werden"; Vielmehr etwas auf der Ebene von „Oh, wir gehen eine verknüpfte Liste von 100.000 Elementen durch“. Ich war optimistisch und hoffte, dass ich den Prozess erheblich beschleunigen konnte, nachdem ich es herausgefunden hatte.

Statistiken helfen nicht


Der erste Ort, an dem ich nach Hinweisen suchte, war die pbrt-Dump-Statistik nach dem Rendern. Die Hauptphasen der pbrt-Ausführung sind so konfiguriert, dass Sie ungefähre Profildaten erfassen können, indem Sie Vorgänge mit periodischen Unterbrechungen während des Rendervorgangs korrigieren. Leider hat uns die Statistik nicht viel geholfen: Berichten zufolge wurden fast 35 Minuten vor Beginn des Renderns 4 Minuten und 22 Sekunden für den Aufbau des BVH aufgewendet, aber für den Rest der Zeit wurden keine Details angegeben.

Das Erstellen von BVH ist die einzige wichtige Rechenaufgabe, die beim Parsen von Szenen ausgeführt wird. Alles andere ist im Wesentlichen eine Deserialisierung von Geometrie und Materialbeschreibungen. Wenn man wusste, wie viel Zeit für die Erstellung des BVH aufgewendet wurde, konnte man verstehen, wie (un) effektiv das System war: Die verbleibende Zeit, nämlich etwa 30 Minuten, analysierte 29 GB Daten, dh die Geschwindigkeit betrug 16,5 MB / s. Gut optimierte JSON-Parser, die im Wesentlichen dieselbe Aufgabe ausführen, arbeiten mit einer Geschwindigkeit von 50 bis 200 MB / s. Natürlich gibt es noch Verbesserungspotenzial.

Um besser zu verstehen, wie viel Zeit verschwendet wird, habe ich pbrt mit einem Linux- Perf- Tool gestartet, das ich noch nie zuvor verwendet hatte. Aber anscheinend hat er die Aufgabe gemeistert. Ich wies ihn an, nach DWARF-Zeichen zu suchen, um Funktionsnamen zu erhalten ( --call-graph dwarf ), und um keine 100-Gigabyte-Trace-Dateien zu erhalten, musste ich die Abtastrate von 4000 auf 100 Abtastungen pro Sekunde ( -F 100 ) senken. Aber mit diesen Parametern lief alles gut und ich war angenehm überrascht, dass das perf report Tool eine Oberfläche mit netten Flüchen hat.

Folgendes könnte er mir sagen, nachdem er mit pbrt angefangen hat:


Ich habe nicht gescherzt, als ich über die "Schnittstelle mit schönen Flüchen" sprach.

Wir sehen, dass mehr als die Hälfte der Zeit für die Analyse der Mechanik yyparse() : yyparse() ist das von Bison erzeugte yylex() und yylex() ist der von flex erzeugte lexikalische Analysator (Lexer). Mehr als die Hälfte der Zeit in yylex() wird für strtod() , das Zeichenfolgen in doppelte Werte konvertiert. Wir werden yyparse() Angriff auf yyparse() und yylex() den dritten Artikel in dieser Reihe verschieben, aber jetzt können wir bereits verstehen, dass es eine gute Idee sein könnte, die in den Renderer geworfene Datenmenge zu reduzieren.

Vom Text zum PLY


Eine Möglichkeit, weniger Zeit mit dem Parsen von Textdaten zu verbringen, besteht darin, die Daten in ein Format zu konvertieren, das effizienter analysiert wird. Die meisten der 29 GB dieser Szenenbeschreibungsdateien sind Dreiecksnetze, und pbrt unterstützt bereits das PLY-Format , eine effektive binäre Darstellung von Polygonnetzen. Außerdem gibt es in pbrt ein Befehlszeilenflag --toply , das die Szenenbeschreibungsdatei pbrt analysiert, alle gefundenen Dreiecksnetze in PLY-Dateien konvertiert und eine neue pbrt-Datei erstellt, die auf diese PLY-Dateien verweist.

Der Haken ist, dass ptex- Texturen in der Disney-Szene aktiv verwendet werden, was wiederum erfordert, dass jedem Dreieck ein faceIndex Wert zugeordnet wird, der bestimmt, von welcher Fläche des ursprünglichen Teilnetzes es stammt. Um diese Werte zu übertragen, genügte es, einfach Unterstützung für neue Felder in der PLY-Datei hinzuzufügen . Weitere Untersuchungen ergaben, dass beim Konvertieren jedes Netzes - selbst wenn es nur ein Dutzend Dreiecke enthält - in eine PLY-Datei Zehntausende kleiner PLY-Dateien im Ordner erstellt werden, was zu eigenen Leistungsproblemen führt. Wir haben es geschafft, dieses Problem zu beseitigen, indem wir der Implementierung die Möglichkeit hinzugefügt haben , kleine Netze unverändert zu lassen .

Ich habe ein kleines Befehlszeilenskript geschrieben , um alle *_geometry.pbrt Dateien in einen Ordner zu konvertieren und PLY für große Netze zu verwenden. Beachten Sie, dass es fest codierte Annahmen zu Pfaden gibt, die geändert werden müssen, damit das Skript an anderer Stelle funktioniert.

Erster Geschwindigkeitsschub


Nach dem Konvertieren aller großen Netze in PLY verringerte sich die Größe der Szenenbeschreibung auf der Festplatte von 29 auf 22 GB: 16,9 GB pbrt-Szenendateien und 5,1 GB PLY-Binärdateien. Nach der Konvertierung verringerte sich die Gesamtzeit der ersten Stufe des Systems auf 27 Minuten und 35 Sekunden, und die Einsparungen beliefen sich auf 7 Minuten und 23 Sekunden, dh wir beschleunigten um das 1,3-fache 3 . Das Verarbeiten einer PLY-Datei ist viel effizienter als das Verarbeiten einer pbrt-Textdatei: Nur 40 Sekunden Startzeit wurden für das Parsen von PLY-Dateien aufgewendet, und wir sehen, dass PLY-Dateien mit einer Geschwindigkeit von ca. 130 MB / s oder ca. 8-mal schneller als das pbrt-Textformat verarbeitet wurden .

Es war ein guter, einfacher Sieg, aber wir hatten noch viel zu tun.

Beim nächsten Mal werden wir herausfinden, wo der gesamte Speicher tatsächlich verwendet wird, hier einige Fehler beheben und dabei noch mehr Geschwindigkeit erzielen.

Anmerkungen


  1. Sie sollten jetzt ein besseres Verständnis für die Motivation haben, meinerseits ptex-Unterstützung hinzuzufügen und Disney BSDF letztes Jahr auf pbrt umzustellen.
  2. Die ganze Zeit hier und in nachfolgenden Beiträgen ist für die WIP-Version (Work In Progress) angegeben, mit der ich vor der offiziellen Veröffentlichung gearbeitet habe. Es scheint, dass die endgültige Version etwas größer ist. Wir werden uns an die Ergebnisse halten, die ich bei der Arbeit mit der Originalszene aufgenommen habe, obwohl sie nicht ganz den Ergebnissen der endgültigen Version entsprechen. Ich vermute, dass die Lehren aus ihnen die gleichen sein können.
  3. Beachten Sie, dass die Geschwindigkeitssteigerung im Wesentlichen dem entspricht, was Sie bei einer Reduzierung des Parsing-Datenvolumens um ca. 50 Prozent erwarten würden. Die Zeit, die wir laut Profiler verbringen, bestätigt unsere Idee.

Source: https://habr.com/ru/post/de417407/


All Articles