Three.js: Nicht blockierende Asset-Lader

Erstellt am 11. Juli 2017  ·  53Kommentare  ·  Quelle: mrdoob/three.js

Wie in https://github.com/mrdoob/three.js/issues/11301 besprochen, ist eines der Hauptprobleme, die wir in WebVR haben, obwohl es auch bei Nicht-VR-Erlebnissen ärgerlich ist, das Blockieren des Hauptthreads beim Laden von Assets.

Mit der jüngsten Implementierung von Link-Traversal im Browser ist nicht-blockierendes Laden ein Muss, um eine zufriedenstellende Benutzererfahrung zu gewährleisten. Wenn Sie von einer Seite zur anderen springen und die Zielseite beginnt, Assets zu laden, die den Mainthread blockieren, wird die Render-Funktion blockiert, sodass keine Frames an das Headset gesendet werden und der Browser uns nach einer kurzen Nachfrist aus VR schmeißt und der Benutzer muss das Headset herausnehmen, erneut auf VR eingeben klicken (Benutzergeste erforderlich) und zum Erlebnis zurückkehren.

Derzeit können wir zwei Implementierungen des nicht blockierenden Ladens von OBJ-Dateien sehen:

  • (2) Mainthread-Versprechen mit verzögertem Parsing mit setTimeOut: Oculus ReactVR : Dieser Loader liest weiterhin Zeilen mit kleinen Zeitschlitzen, um das Blockieren des Hauptthreads durch Aufrufen von setTimeout zu verhindern: https://github.com/facebook/react-vr/blob/master /ReactVR/js/Loaders/WavefrontOBJ/OBJParser.js#L281 -L298
    Bei diesem Ansatz wird das Laden langsamer, da wir nur einige Zeilen in jedem Zeitschlitz parsen, aber der Vorteil besteht darin, dass wir nach Abschluss des Parsings die DREI Objekte ohne zusätzlichen Overhead einsatzbereit haben.

Beides hat seine Vor- und Nachteile und ich bin ehrlich gesagt kein Experte für Webworker, um diese Implementierung zu bewerten, aber es ist eine interessante Diskussion, die idealerweise zu einem generischen Modul führen würde, das verwendet werden könnte, um die Loader auf eine nicht blockierende Version zu portieren.

Irgendwelche Vorschläge?

/cc @mikearmstrong001 @kaisalmen @delapuente @spite

Suggestion

Hilfreichster Kommentar

Erste Vorschlagsveröffentlichung von THREE.UpdatableTexture

Idealerweise sollte es Teil jeder THREE.Texture sein, aber ich würde diesen Ansatz zuerst untersuchen.

Alle 53 Kommentare

Sie können einen auf Versprechen + Arbeiter + inkrementell basierenden Lader haben (ein bisschen wie eine Mischung aus beiden Punkten)

Übergeben Sie die Quell-URL an das Worker-Skript, rufen Sie die Ressourcen ab, geben Sie eine Struktur übertragbarer Objekte mit den erforderlichen Puffern, Strukturen und sogar ImageBitmaps zurück; Es sollte einfach genug sein, um nicht viel Aufwand für die Verarbeitung von drei.js zu benötigen.

Das Hochladen von Daten auf die GPU wird trotzdem blockiert, aber Sie können über display.rAF eine Warteschlange erstellen, um die Befehle auf verschiedene Frames zu verteilen. Die Befehle können einzeln pro Frame ausgeführt werden oder die durchschnittliche Zeit der Operation berechnen und so viele ausführen, wie im aktuellen Frame-Budge "sicher" ausgeführt werden können (so etwas wie requestIdleCallback wäre schön, wird aber nicht allgemein unterstützt , und es ist in WebVR-Sitzungen problematisch). Kann auch durch die Verwendung von bufferSubData, texSubImage2D usw. verbessert werden.

Die Unterstützung für Arbeiter und übertragbare Objekte ist derzeit ziemlich solide, insbesondere in WebVR-fähigen Browsern.

Hallo zusammen, ich habe einen Prototyp zur Verfügung, der in diesem Zusammenhang für Sie von Interesse sein könnte. Siehe folgenden Zweig:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons
Hier wurde der Teil der Mesh-Bereitstellung vollständig von WWOBJLoader2 :
https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/WWLoaderCommons.js

WWLoaderCommons erleichtert die Implementierung anderer Mesh-Anbieter (Dateiformat-Loader). Im Wesentlichen definiert es, wie eine Web-Worker-Implementierung Mesh-Daten an den Hauptthread zurückgeben und in die Szene verarbeiten/integrieren muss. Sehen Sie sich den zufälligen Dreiecks-Junk-Anbieter 😉 an, der als Tech-Demonstrator dient:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons/test/meshspray
https://kaisalmen.de/proto/test/meshspray/main.src.html

Sogar in der aktuellen Implementierung verlässt sich WWOBJLoader2 auf übertragbare Objekte (ArrayBuffers/ByteBuffers), um die Rohdaten BufferedGeometry für die Mesh vom Worker zum Hauptthread bereitzustellen. Zeitlich ist die Erzeugung der Mesh aus den bereitgestellten ByteBuffern vernachlässigbar. Immer wenn ein größeres Mesh in die Szene integriert wird, kommt das Rendering jedoch zum Stillstand (Datenkopien, Szenengraph-Anpassungen ... !?). Dies ist unabhängig von der Quelle immer der Fall (korrigiert mich, wenn ich falsch liege).
Der "Stream"-Modus von WWOBJLoader2 glättet diese Blockaden, aber wenn ein einzelnes Mesh-Teil Ihres OBJ-Modells 0,5 Millionen Vertices wiegt, wird das Rendern für einen längeren Zeitraum angehalten.

Ich habe eine neue Ausgabe geöffnet, in der detailliert beschrieben wird, was ich genau in der maßgeschneiderten Filiale gemacht habe und warum:
https://github.com/kaisalmen/WWOBJLoader/issues/11
Das Thema ist noch ein Stummel und Details folgen in Kürze.

Um einige Zahlen zu nennen, hier ist ein Leistungsprofil von https://threejs.org/examples/webgl_loader_gltf2.html , das ein 13 MB-Modell mit 2048 x 2048-Texturen lädt.

screen shot 2017-07-11 at 10 11 42 pm

In diesem Fall blockiert das primäre Blockieren des Hauptthreads das Hochladen von Texturen auf die GPU, und soweit ich weiß, kann dies nicht von einem WW aus erfolgen.

Für Neugierige ist der letzte Block, der den Hauptthread blockiert, das Hinzufügen einer Umgebungs-Cubemap.

Das Hauptziel von React-VR ist nicht unbedingt, den optimalen Loader in Bezug auf die Wanduhrzeit zu haben, sondern keine plötzlichen und unerwarteten Frame-Outs zu verursachen, wenn neue Inhalte geladen werden. Alles, was wir tun können, um dies zu minimieren, ist für alle von Vorteil, insbesondere für VR.

Texturen sind definitiv ein Problem und ein offensichtlicher erster Schritt wäre, sie optional inkrementell zu laden - eine Reihe von Zeilen auf einmal für eine große Textur. Da der Upload für die Client-Programme verborgen ist, wird es für sie schwierig zu verwalten sein, aber ich wäre dafür, dass dies dem webgl-Renderer offener ausgesetzt würde, um den Druck von Three.js zu nehmen

Für das gltf-Parsing sehe ich bei meinen Tests häufig das Blockieren von 500 ms, dies ist wichtig und ich würde einen inkrementellen Ansatz für alle Loader bevorzugen (der auch klonbar sein sollte).

Die Prämisse von React VR besteht darin, einfache dynamische Inhalte zu fördern, die von einem Webstil gesteuert werden, um mehr Entwickler zu ermutigen, und dies wird mehr Wert auf die Verbesserung der dynamischen Handhabung legen. Meistens wissen wir nicht, welche Assets zu Beginn unserer benutzererstellten Anwendungen benötigt werden.

@kaisalmen Danke für den Link

In Elation Engine / JanusWeb führen wir tatsächlich alle unsere Modellanalysen mithilfe eines Pools von Worker-Threads durch, was ziemlich gut funktioniert. Sobald die Arbeiter das Laden jedes Modells abgeschlossen haben, serialisieren wir es mit object.toJSON() , senden es mit postMessage() an den Hauptthread und laden es dann mit ObjectLoader.parse() . Dadurch werden die meisten blockierenden Teile des Ladecodes entfernt - es wird immer noch einige Zeit in ObjectLoader.parse() die wahrscheinlich optimiert werden könnte, aber die allgemeine Interaktivität und Ladegeschwindigkeit werden drastisch verbessert. Da wir einen Pool von Workern verwenden, können wir auch mehrere Modelle parallel parsen, was in komplexen Szenen ein großer Gewinn ist.

Was die Texturen angeht, ja, ich denke, dass einige Änderungen an der Textur-Upload-Funktion von Three.js erforderlich sind. Ein Chunked-Uploader mit texSubImage2D wäre ideal, dann könnten wir, wie oben erwähnt, Teilaktualisierungen großer Texturen über mehrere Frames durchführen.

Ich würde mich sehr freuen, an dieser Änderung mitzuarbeiten, da sie vielen Projekten zugute kommt, die Three.js als Basis verwenden

Ich denke, texSubImage2D ist eine gute Idee.
Aber auch, warum WebGL Texturen nicht asynchron hochlädt.
OpenGL und andere Bibliotheken haben die gleiche Einschränkung?

Und eine andere Sache, die ich denke, ist die GLSL-Kompilierung.
Lässt es den Rahmen fallen? Oder schnell genug und wir brauchen uns nicht darum zu kümmern?

Ja, dies ist auch in nativem OpenGL ein Problem - das Kompilieren von Shadern und das Hochladen von Bilddaten sind synchrone / blockierende Operationen. Aus diesem Grund empfehlen die meisten Game-Engines oder zwingen Sie sogar, alle Inhalte vorab zu laden, bevor Sie mit dem Level beginnen - es wird im Allgemeinen als zu viel Leistung angesehen, neue Ressourcen sogar von einer Festplatte zu laden, und hier versuchen wir, dies asynchron zu tun über das Internet ... wir haben tatsächlich ein schwierigeres Problem als die meisten Spieleentwickler und müssen auf fortschrittlichere Techniken zurückgreifen, wenn wir in der Lage sein wollen, neue Inhalte im Handumdrehen zu streamen.

Das Hochladen von Texturen wird weniger problematisch sein, wenn wir in Zukunft die neue ImageBitmap API verwenden. Siehe https://youtu.be/wkDd-x0EkFU?t=82 .

Übrigens: Dank @spite haben wir bereits einen experimentellen ImageBitmapLoader im Projekt.

@Mugen87 eigentlich mache ich bereits alle meine Textur-Ladevorgänge mit ImageBitmap in Elation Engine / JanusWeb - es hilft definitiv und lohnt sich, in den Three.js-Kern zu integrieren, aber es gibt zwei Hauptkosten, die mit der Verwendung von Texturen in WebGL verbunden sind - Bilddecodierungszeit , und Bild-Upload-Zeit - ImageBitmap hilft nur beim ersten.

Dies verkürzt die Zeit, die die CPU blockiert, in meinen Tests um etwa 50%, aber das Hochladen großer Texturen auf die GPU, insbesondere 2048 x 2048 und höher, kann leicht eine Sekunde oder länger dauern.

Es wäre praktisch, das auszuprobieren, was @jbaicoianu vorschlägt. Wie auch immer, wenn Sie sich für die Hauptthread-Alternative entscheiden, scheint dies eine perfekte Ergänzung für

Ich stimme Ihnen allen zu, ich glaube, der Ansatz, alles auf dem Worker zu laden und zu parsen, die benötigten Objekte wieder im Hauptthread zu erstellen (wenn es sehr teuer ist, könnte es in mehreren Schritten erfolgen) und dann ein inkrementelles Laden in den Renderer einzubeziehen.
Für ein MVP könnten wir einen maxTexturesUploadPerFrame (standardmäßig unendlich) definieren und das Rendern kümmert sich um das Laden aus dem Pool entsprechend dieser Zahl.
In den folgenden Iterationen könnten wir, wie @spite kommentierte, eine Logik

requestIdleCallback wäre schön, wird aber nicht allgemein unterstützt und ist in WebVR-Sitzungen problematisch

@trotz Ich bin neugierig auf deinen Satz, was meinst du mit problematisch?

Ich habe eine THREE.UpdatableTexture, um Texturen mit texSubImage2D inkrementell zu aktualisieren, benötigt jedoch ein wenig Optimierung von Three.js. Die Idee ist, eine PR vorzubereiten, um Unterstützung hinzuzufügen.

Bezüglich requestIdleCallback (rIC):

  • Erstens wird es von Chrome und Firefox unterstützt, und obwohl es leicht mehrfach gefüllt werden kann, kann die polygefüllte Version den Zweck leicht zunichte machen.

  • zweitens: genauso wie bei der Präsentation vrDisplay.requestAnimationFrame (rAF) anstelle von window.rAF aufgerufen werden muss, gilt das gleiche für rIC, wie in diesem crbug besprochen . Das bedeutet, dass der Lader die aktuelle aktive Anzeige jederzeit kennen muss, oder er stoppt, je nachdem, was präsentiert wird. Es ist nicht allzu kompliziert, es macht nur die Verkabelung der Lader (die idealerweise unabhängig vom Präsentationszustand nur ihre Arbeit machen sollten) komplexer. Eine andere Möglichkeit besteht darin, den Teil in Threejs zu haben, der inkrementelle Jobs im Hauptthread ausführt, um die aktuelle Anzeige gemeinsam zu nutzen; Ich denke, es ist jetzt mit den neuesten Änderungen an VR in Threejs viel einfacher.

Eine weitere Überlegung: Um eine große Textur in mehreren Schritten mit texSubImage2D (256x256 oder 512x512) hochladen zu können, benötigen wir einen WebGL2-Kontext mit Offset- und Clipping-Funktionen. Ansonsten müssen die Bilder vor dem Hochladen per Canvas vorgeschnitten, grundsätzlich clientseitig gekachelt werden.

@site Guter Punkt, ich habe nicht daran gedacht, dass rIC beim Präsentieren nicht aufgerufen wird. Zuerst dachte ich, dass wir eine display.rIC brauchen sollten, aber ich glaube, dass die .rIC an Fenster angehängt und aufgerufen werden sollte, wenn Fenster oder Display sind beide im Leerlauf.
Ich glaube, ich habe in den Diskussionen zu den Webvr-Spezifikationen nichts dazu gehört. @kearwood hat vielleicht mehr Informationen, aber es ist definitiv ein Problem, das wir

Wir freuen uns auf Ihre UpdateableTexture PR! :) Auch wenn es nur ein WIP ist, könnten wir einen Teil der Diskussion dorthin verschieben.

Vielleicht könnten Lader so etwas werden...

THREE.MyLoader = ( function () {

    // parse file and output js object
    function parser( text ) {
        return { 'vertices': new Float32Array() }
    }

    // convert js object to THREE objects.
    function builder( data ) {
        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute( new THREE.BufferAttribute( data.vertices, 3 );
        return geometry;
    }

    function MyLoader( manager ) {}
    MyLoader.prototype = {
        constructor: MyLoader,
        load: function ( url, onLoad, onProgress, onError  ) {},
        parse: function ( text ) {
            return builder( parser( text ) );
        },
        parseAsync: function ( text, onParse ) {
            var code = parser.toString() + '\nonmessage = function ( e ) { postMessage( parser( e.data ) ); }';
            var blob = new Blob( [ code ], { type: 'text/plain' } );
            var worker = new Worker( window.URL.createObjectURL( blob ) );
            worker.addEventListener( 'message', function ( e ) {
                onParse( builder( e.data ) );
            } );
            worker.postMessage( text );
        }
    }
} )();

Erste Vorschlagsveröffentlichung von THREE.UpdatableTexture

Idealerweise sollte es Teil jeder THREE.Texture sein, aber ich würde diesen Ansatz zuerst untersuchen.

@mrdoob Ich sehe den Vorteil, dass genau derselbe Code an den Arbeiter weitergeleitet wird, es fühlt sich einfach soooo falsch an 😄. Ich frage mich, welche Auswirkungen eine Serialisierung, Blobbing und Neubewertung des Drehbuchs haben würde; nichts Schlimmes, aber ich glaube nicht, dass der Browser für diese Macken optimiert ist 🙃

Idealerweise würde auch der Abruf der Ressource selbst im Worker erfolgen. Und ich denke, die Methode parser() im Browser würde ein importScripts von Three.js selbst benötigen.

Aber ein einziger Punkt zum Definieren von Sync/Async-Loadern wäre super!

@mrdoob die builder Funktion könnte völlig generisch und allen Loadern gemeinsam sein (WIP: https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL215- LL367; Update: noch nicht in einer Funktion isoliert). Wenn die Eingabedaten auf reine js-Objekte ohne Verweis auf THREE Objekte beschränkt sind (das haben Sie im Sinn, oder?), könnten wir serialisierbaren Worker-Code erstellen, ohne dass im Worker importiert werden muss (was WWOBJLoader tut). Dies ist für Geometrie einfach, aber Materialien/Shader (sofern in Datei definiert) könnten dann nur im Builder erstellt und zuvor nur als JSON durch das parser .
Ein Worker sollte jedes neue Mesh und dessen Fertigstellung signalisieren, denke ich. Es könnte so geändert werden:

// parse file and output js object
function parser( text, onMeshLoaded, onComplete ) {
    ....
}
parse: function ( text ) {
    var node = new THREE.Object3d();
    var onMeshLoaded = function ( data ) {
        node.add( builder( data ) );
    };

    // onComplete as second callbackonly provided in async case
    parser( text, onMeshLoaded ) );
    return node;
},

Ein Worker-Builder-Util ist hilfreich + ein generisches Kommunikationsprotokoll, das Ihrer Idee, den Parser so zu verwenden, nicht widerspricht, aber ich denke, es muss etwas umhüllt werden. Aktueller Stand der WWOBJLoader-Entwicklung: https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL40 -LL133, während Frontend-Aufrufe report_progress, meshData und complete sind.

Aktualisierung2:

  • Denken Sie, dass der Parser zustandslos sein sollte? Für builder ist das in parser anzupassen. Dies impliziert auch, dass Konfigurationsparameter unabhängig vom Parsing auf den Worker übertragbar sein sollten
  • Es wäre cool, so etwas wie eine Run-Funktion zu haben, die ein generisches Konfigurationsobjekt frisst. Ein generischer Director könnte dann jeden Loader mit Anweisungen füttern (dies funktioniert jetzt im maßgeschneiderten Commons-Zweig von WWOBJLoader , übrigens).
  • laufende Entwicklung: WWOBJLoader2 jetzt OBJLoader und überschreibt das Parsen. Wir haben also beide Parsing-Caps, aber in verschiedenen Klassen. Es kommt dem Vorschlag nahe, ist aber noch nicht im Einklang. Ein Teil des Parser-Codes muss vereinheitlicht werden und schließlich müssen beide Klassen fusioniert werden

Das war es fürs Erste. Feedback willkommen

@mrdoob Ich mag die Idee, den Worker im

Ich mag den Ansatz, ein reduziertes Übertragungsformat für die Übergabe zwischen Arbeitern zu verwenden, da es einfach ist, diese TypedArrays als übertragbar zu markieren, wenn sie an den Haupt-Thread zurückgegeben werden. In meinem aktuellen Ansatz verwende ich die Methode .toJSON() im Worker, aber dann gehe ich durch und ersetze die JS-Arrays für Vertices, UVs usw. durch den entsprechenden TypedArray-Typ und markiere sie beim Aufrufen als übertragbar POST-Meldung. Dies macht die Analyse/Speichernutzung im Hauptthread etwas leichter, auf Kosten von etwas mehr Verarbeitungs-/Speichernutzung im Worker - es ist ein guter Kompromiss, aber es könnte effizienter gemacht werden, indem man entweder a . einführt neues Übertragungsformat, wie Sie es vorschlagen, oder indem Sie .toJSON() ändern, um uns optional TypedArrays anstelle von JS-Arrays zu geben.

Die zwei Nachteile, die ich bei diesem vereinfachten Ansatz sehe, sind:

  • Erfordert eine Überarbeitung der vorhandenen Modelllader. Viele Loader verwenden eingebaute DREI Namespaced-Klassen und -Funktionen, um ihre Aufgabe auszuführen, daher müssten wir wahrscheinlich einen minimalen Satz von Three.js-Code einfügen - und mit Workern wird das schwierig
  • Das Übertragungsformat sollte eine Objekthierarchie sowie verschiedene Objekttypen (Mesh, SkinnedMesh, Light, Camera, Object3D, Line usw.)

@spite Bezüglich "Außerdem würde das Abrufen der Ressource selbst idealerweise im Worker erfolgen." - das war mein Gedanke, als ich zum ersten Mal den Worker-basierten Asset-Loader für Elation Engine implementierte - Ich hatte einen Pool von 4 oder 8 Workern, und ich übergab ihnen Jobs, sobald sie verfügbar wurden, und dann holten die Worker die Dateien und analysierten sie sie und kehren Sie zum Haupt-Thread zurück. In der Praxis bedeutete dies jedoch, dass die Downloads das Parsen blockieren würden und Sie die Vorteile verlieren würden, die Sie durch Pipelining usw. erhalten würden, wenn Sie sie alle auf einmal anfordern würden.

Als uns dies klar wurde, haben wir eine weitere Ebene hinzugefügt, um alle unsere Asset-Downloads zu verwalten, und dann löst der Asset-Downloader Ereignisse aus, um uns zu informieren, wenn Assets verfügbar werden. Wir geben diese dann an den Worker-Pool weiter, indem wir übertragbare Daten für die Binärdateidaten verwenden, um sie effizient in den Worker zu bringen. Mit dieser Änderung laufen die Downloads alle schneller ab, obwohl sie sich im Hauptthread befinden, und die Parser können die Verarbeitung mit vollem Umfang ausführen, anstatt mit ihren Daumen zu drehen und auf Daten zu warten. Insgesamt erwies sich dies als eine der besten Optimierungen, die wir in Bezug auf die Ladegeschwindigkeit von Assets vorgenommen haben.

Zum Thema Laden von Texturen habe ich einen Proof of Concept einer neuen FramebufferTexture -Klasse erstellt, die mit einem begleitenden FramebufferTextureLoader . Dieser Texturtyp erweitert WebGLRenderTarget , und sein Loader kann so konfiguriert werden, dass Texturen in Blöcke einer bestimmten Größe geladen und mit requestIdleCallback() in den Framebuffer komponiert werden.

https://baicoianu.com/~bai/three.js/examples/webgl_texture_framebuffer.html

Wählen Sie in diesem Beispiel einfach eine Bildgröße und eine Kachelgröße aus und der Ladevorgang wird gestartet. Zuerst initialisieren wir die Textur auf reines Rot. Wir starten den Download der Bilder (sie sind ungefähr 10 MB groß, also gib ein bisschen Zeit), und wenn sie fertig sind, ändern wir den Hintergrund auf blau. An diesem Punkt beginnen wir, das Bild mit createImageBitmap() zu parsen, um die Datei zu parsen, und wenn das fertig ist, richten wir eine Reihe von Leerlauf-Callbacks ein, die weitere Aufrufe an createImageBitmap() die das Bild effizient in Kacheln aufteilen . Diese Kacheln werden über mehrere Frames in den Framebuffer gerendert und haben einen deutlich geringeren Einfluss auf die Framezeiten als alles auf einmal.

HINWEIS - FireFox scheint derzeit nicht alle Versionen von createImageBitmap zu implementieren und gibt derzeit einen Fehler für mich aus, wenn es versucht, in Kacheln aufzuteilen. Daher funktioniert diese Demo derzeit nur in Chrome. Hat jemand eine Referenz für eine createImageBitmap Support-Roadmap in FireFox?

Ich muss einige Aufräumarbeiten durchführen, dieser Prototyp ist ein bisschen chaotisch, aber ich bin sehr zufrieden mit den Ergebnissen und sobald ich einen Weg gefunden habe, die browserübergreifenden Probleme (Leinwand-Fallback usw.) zu umgehen, bin ich erwägen, dies als Standard für alle Texturen in JanusWeb zu verwenden. Der Einblendeffekt ist auch irgendwie ordentlich, und wir könnten uns sogar eine verkleinerte Version ausdenken und zuerst eine verkleinerte Version machen und dann nach und nach die detailreicheren Kacheln laden.

Gibt es irgendwelche leistungs- oder funktionsbezogenen Gründe, warum es eine schlechte Idee sein könnte, einen Framebuffer für jede Textur in der Szene zu haben, im Gegensatz zu einer Standardtexturreferenz? Ich konnte nichts über max. Framebuffer pro Szene, soweit ich das beurteilen kann, sobald ein Framebuffer eingerichtet wurde, wenn Sie ihn nicht rendern, ist es dasselbe wie bei jeder anderen Texturreferenz, aber ich habe das Gefühl, dass mir etwas Offensichtliches fehlt warum das eine wirklich schlechte idee wäre :)

@jbaicoianu re: firefox's createImageBitmap, der Grund dafür ist, dass sie den Dictionary-Parameter nicht unterstützen, also nicht die Bildausrichtung oder Farbraumkonvertierung. es macht die meisten Anwendungen der API ziemlich nutzlos. Ich habe zwei Fehler im Zusammenhang mit dem Problem eingereicht: https://bugzilla.mozilla.org/show_bug.cgi?id=1367251 und https://bugzilla.mozilla.org/show_bug.cgi?id=1335594

@trotz das habe ich auch gedacht, ich habe diesen Fehler gesehen, dass das

Argument 4 of Window.createImageBitmap '1024' is not a valid value for enumeration ImageBitmapFormat.

Was verwirrend ist, denn ich sehe keine Version von createImageBitmap in der Spezifikation, die ein ImageBitmapFormat als Argument annimmt.

Gibt es irgendwelche leistungs- oder funktionsbezogenen Gründe, warum es eine schlechte Idee sein könnte, einen Framebuffer für jede Textur in der Szene zu haben, im Gegensatz zu einer Standardtexturreferenz? Ich konnte nichts über max. Framebuffer pro Szene, soweit ich das beurteilen kann, sobald ein Framebuffer eingerichtet wurde, wenn Sie ihn nicht rendern, ist es dasselbe wie bei jeder anderen Texturreferenz, aber ich habe das Gefühl, dass mir etwas Offensichtliches fehlt warum das eine wirklich schlechte idee wäre :)

@jbaicoianu THREE.WebGLRenderTarget hält einen Framebuffer, eine Textur und einen Renderpuffer. Wenn Sie die Textur zusammengestellt haben, können Sie den Framebuffer und den Renderbuffer löschen und nur die Textur behalten. So etwas sollte dies tun (nicht getestet):

texture = target.texture;
target.texture = null; // so the webgl texture is not deleted by dispose()
target.dispose();

@wrr das ist gut zu wissen, danke. Ich muss auch hier definitiv auf die Speichereffizienz verzichten - es stürzt unweigerlich irgendwann ab, wenn Sie die Parameter genug ändern, also weiß ich, dass ich noch nicht aufgeräumt habe. Für weitere Hinweise wie diesen wäre ich sehr dankbar.

@mrdoob und @jbaicoianu Ich habe vergessen zu erwähnen, dass mir die Idee auch gefällt. 😄
Ich habe den Code von OBJLoader und WWOBJLoader und allen Beispielen ( code ) aufgeräumt (überarbeitetes Init, Worker-Instruktions-Objekt, Müll-Multi-Callback-Behandlung ersetzt, allgemeine Ressourcenbeschreibung usw.). Beide Lader können nun kombiniert werden. Sie werden Ihrem Bauplan hoffentlich irgendwann nächste Woche folgen, abhängig von meiner Freizeit:
Angeleiteter WWOBJLoader2-Test:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Angeleiteter Benutzer von generischem WorkerSupport :
https://kaisalmen.de/proto/test/meshspray/main.src.html
Der große gezippte OBJ-Dateitest:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Ich werde die obigen Beispiele mit neuerem Code aktualisieren, sobald verfügbar, und Sie darüber informieren.
Update 30.07.2017: OBJLoader2 und WWOBJLoader2 jetzt identische Parser. Sie übergeben Daten direkt oder vom Worker an die allgemeine Builder-Funktion.
Update 31.07.2017: WWOBJLoader2 ist weg. OBJLoader2 bietet parse und parseAsync , load und run (Feed durch LoaderDirector oder manuell)

Update 2017-08-09:
Update in neuen Beitrag verschoben.

OBJLoader2 ist wieder signatur- und verhaltenskompatibel mit OBJLoader (ich habe dies während der Evolution gebrochen), OBJLoader2 bietet parseAsync und load mit useAsync Flag zusätzlich. Ich denke, es ist jetzt bereit, V2.0.0-Beta genannt zu werden. Hier finden Sie den aktuellen Entwicklungsstand:
https://github.com/kaisalmen/WWOBJLoader/tree/V2.0.0-Beta/src/loaders

Ich habe LoaderSupport-Klassen (unabhängig von OBJ) extrahiert, die als Dienstprogramme und erforderliche Support-Tools dienen. Sie könnten für potenzielle andere arbeiterbasierte Lader wiederverwendet werden. Den gesamten Code unten habe ich unter den Namensraum THREE.LoaderSupport , um seine Abhängigkeit von OBJLoader2 hervorzuheben:

  • Builder : Für allgemeines Mesh-Gebäude
  • WorkerDirector : Erstellt Lader über Reflektion, verarbeitet PrepData in der Warteschlange mit der konfigurierten Anzahl von Arbeitern. Wird verwendet, um Lader vollständig zu automatisieren (MeshSpray- und Parallels-Demo)
  • WorkerSupport : Dienstprogrammklasse zum Erstellen von Workern aus vorhandenem Code und zum Erstellen eines einfachen Kommunikationsprotokolls
  • PrepData + ResourceDescriptor : Beschreibung zur Automatisierung oder einfach als einheitliche Beschreibung unter Beispielen
  • Commons : Mögliche Basisklasse für Lader (bündelt allgemeine Parameter)
  • Callbacks : (onProgress, onMeshAlter, onLoad) wird für die Automatisierung und Richtung verwendet und LoadedMeshUserOverride wird verwendet, um Informationen von onMeshAlter (normale Hinzufügung im objloader2-Test unten)
  • Validator : Überprüfungen auf null/undefinierte Variablen

@mrdoob @jbaicoianu OBJLoader2 umschließt jetzt einen Parser wie vorgeschlagen (er ist mit Parametern konfiguriert, die global gesetzt oder von PrepData für die Ausführung empfangen wurden). Das Builder empfängt jedes einzelne Rohmesh und der Parser gibt den Basisknoten zurück, aber ansonsten entspricht er dem Blueprint.
In OBJLoader2 befindet sich noch Hilfscode für die Serialisierung des Parsers, der wahrscheinlich nicht benötigt wird.
Der Builder muss bereinigt werden, da das Vertrags-/Parameterobjekt für die buildMeshes Funktion immer noch stark vom OBJ-Laden beeinflusst wird und daher noch im Aufbau betrachtet wird.

Der Code muss noch etwas poliert werden, aber dann ist er bereit für Feedback, Diskussion, Kritik usw... 😄

Beispiele und Tests

OBJ Loader mit Ausführen und Laden:
https://kaisalmen.de/proto/test/objloader2/main.src.html
OBJ Loader mit run async und parseAsync:
https://kaisalmen.de/proto/test/wwobjloader2/main.src.html
Gezielte Verwendung von Run async OBJLoader2:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Gezielte Nutzung von generischem WorkerSupport:
https://kaisalmen.de/proto/test/meshspray/main.src.html
Der große gezippte OBJ-Dateitest:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Gut aussehen! Sind Ihnen diese Änderungen in OBJLoader ? #11871 565c6fd0f3d9b146b9434e5fccfa2345a90a3842

Ja, ich muss das portieren. Ich schlug einige reproduzierbare Perf-Messungen vor. Werde am Wochenende an beiden arbeiten. Wann planen Sie die Veröffentlichung von r87? N-Gon-Support könnte es je nach Datum schaffen.

@mrdoob und voila: https://github.com/mrdoob/three.js/pull/11928 n-gon-Unterstützung

Statusaktualisierung ( Code ):
Die erstellten Worker können nun jeden Parser innerhalb des Workers über Parameter konfigurieren, die von einer Nachricht empfangen werden. WorkerSupport bietet eine Referenz-Worker-Runner-Implementierung ( Code ), die bei Bedarf oder bei Bedarf vollständig durch eigenen Code ersetzt werden kann.
Der Worker erstellt und führt den Parser in der run Methode von WorkerRunnerRefImpl ( Parser ist innerhalb des Worker-Bereichs verfügbar; this.applyProperties ruft Setter oder Eigenschaften von . auf der Parser):

WorkerRunnerRefImpl.prototype.run = function ( payload ) {
    if ( payload.cmd === 'run' ) {

        console.log( 'WorkerRunner: Starting Run...' );

        var callbacks = {
            callbackBuilder: function ( payload ) {
                self.postMessage( payload );
            },
            callbackProgress: function ( message ) {
                console.log( 'WorkerRunner: progress: ' + message );
            }
        };

        // Parser is expected to be named as such
        var parser = new Parser();
        this.applyProperties( parser, payload.params );
        this.applyProperties( parser, payload.materials );
        this.applyProperties( parser, callbacks );
        parser.parse( payload.buffers.input );

        console.log( 'WorkerRunner: Run complete!' );

        callbacks.callbackBuilder( {
            cmd: 'complete',
            msg: 'WorkerRunner completed run.'
        } );

    } else {

        console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );

    }
};

Die Nachricht von OBJLoader2.parseAsync sieht so aus:

this.workerSupport.run(
    {
        cmd: 'run',
        params: {
            debug: this.debug,
            materialPerSmoothingGroup: this.materialPerSmoothingGroup
        },
        materials: {
            materialNames: this.materialNames
        },
        buffers: {
            input: content
        }
    },
    [ content.buffer ]
);

Das Nachrichtenobjekt ist vom Loader abhängig, die Konfiguration des Parsers im Worker ist jedoch generisch.
Der von den verlinkten Beispielen im vorherigen Beitrag verwendete Code wurde mit dem neuesten Code aktualisiert.

Ich denke, die Weiterentwicklung von OBJLoader2 und die Extraktion von Supportfunktionen haben jetzt einen Punkt erreicht, an dem Ihr Feedback erforderlich ist. Wenn alle Beispiele aus seinem Repo in den obigen Zweig portiert wurden, werde ich eine PR mit einer vollständigen Zusammenfassung öffnen und dann Feedback anfordern

Zu Ihrer Information, hier ist ein Work-in-Progress dafür, dass ImageBitmapLoader einen Worker verwendet, wie oben beschrieben. Vielleicht noch interessanter, einige harte Zahlen zu den Ergebnissen: https://github.com/mrdoob/three.js/pull/12456

firefox's createImageBitmap, der Grund dafür ist, dass sie den Dictionary-Parameter nicht unterstützen, also nicht die Bildausrichtung oder Farbraumkonvertierung unterstützt. es macht die meisten Anwendungen der API ziemlich nutzlos.

Das ist unglücklich. ️

@mrdoob Haben Sie einen Plan, ImageLoader in ImageBitmapLoader auf TextureLoader umzustellen, weil ImageBitmap beim Hochladen auf Textur weniger blockieren sollte? createImageBitmap() scheint bisher auf FireFox zu funktionieren, wenn wir nur das erste Argument übergeben. (Vielleicht müssen wir kein zweites und mehr Argumente über TextureLoader ?)

return createImageBitmap( blob );

Es ist eigentlich wichtig, dass createImageBitmap () das Optionswörterbuch unterstützt. Andernfalls können Sie Dinge wie die Bildausrichtung (Flip-Y) nicht ändern oder vormultipliziertes Alpha angeben. Die Sache ist, dass Sie WebGLRenderingContext.pixelStorei für ImageBitmap . Aus der Spezifikation :

_Wenn die TexImageSource eine ImageBitmap ist, werden diese drei Parameter (UNPACK_FLIP_Y_WEBGL, UNPACK_PREMULTIPLY_ALPHA_WEBGL, UNPACK_COLORSPACE_CONVERSION_WEBGL) ignoriert. Stattdessen sollten die entsprechenden ImageBitmapOptions verwendet werden, um eine ImageBitmap mit dem gewünschten Format zu erstellen._

Ich denke, wir können nur zu ImageBitmapLoader wechseln, wenn FF das Optionswörterbuch unterstützt. Außerdem funktionieren Eigenschaften wie Texture.premultiplyAlpha und Texture.flipY derzeit nicht mit ImageBitmap . Ich meine, wenn Benutzer sie festlegen, werden sie eine Textur, die auf ImageBitmap basiert, nicht beeinflussen, was etwas unglücklich ist.

Ach, okay. Ich habe diese Spezifikation übersehen.

Die Bedeutung des Optionswörterbuchs wird auch hier diskutiert:

https://bugzilla.mozilla.org/show_bug.cgi?id=1335594

Die Bugs auf bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) sind dort seit ... zwei Jahren unberührt Jahre jetzt? Ich hätte nicht gedacht, dass sie so verdammt lange brauchen würden, um es zu reparieren.

Das Problem ist also, dass das Feature "technisch" auf FF unterstützt wird, in der Praxis jedoch nutzlos ist. Um es zu verwenden, könnten wir einen Pfad für Chrome haben, der es verwendet, und einen anderen für die anderen Browser, die dies nicht tun. Das Problem ist, da Firefox diese Funktion hat, müssten wir UA-Sniffing durchführen, was scheiße ist.

Die praktische Lösung ist die Feature-Erkennung: Erstellen Sie ein 2x2-Image mit cIB mit dem Flip-Flag, lesen Sie es dann zurück und stellen Sie sicher, dass die Werte korrekt sind.

Bei FireFox-Bugs werde ich sie auch intern kontaktieren. Mal sehen, ob wir einen Workaround brauchen, nachdem wir ihren Plan gehört haben.

Die Bugs auf bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) sind dort seit ... zwei Jahren unberührt Jahre jetzt? Ich hätte nicht gedacht, dass sie so verdammt lange brauchen würden, um es zu reparieren.

Ja, tut mir leid, ich habe es wirklich eine Weile nicht weiterverfolgt -_-

Das Problem ist also, dass das Feature "technisch" auf FF unterstützt wird, in der Praxis jedoch nutzlos ist. Um es zu verwenden, könnten wir einen Pfad für Chrome haben, der es verwendet, und einen anderen für die anderen Browser, die dies nicht tun. Das Problem ist, da Firefox diese Funktion hat, müssten wir UA-Sniffing durchführen, was scheiße ist.

Die praktische Lösung ist die Feature-Erkennung: Erstellen Sie ein 2x2-Image mit cIB mit dem Flip-Flag, lesen Sie es dann zurück und stellen Sie sicher, dass die Werte korrekt sind.

Ja, ich stimme zu, dass beide Lösungen wirklich scheiße sind und wir versuchen sollten, sie zu vermeiden

Ich habe einen ImageBitmap Upload-Leistungstest durchgeführt. Hochladen der Textur alle 5 Sekunden.

Sie können Regular Image mit ImageBitmap vergleichen.

https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html (Normales Bild)
https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html?imagebitmap (ImageBitmap)

An meinen Fenstern sehe ich

| Browser | 8192x4096 JPG 4.4MB | 2048x2048 PNG 4.5MB |
| ---- | ---- | ---- |
| Chrome-Bild | 500ms | 140ms |
| Chrome ImageBitmap | 165ms | 35ms |
| FireFox-Bild | 500ms | 40ms |
| FireFox ImageBitmap | 500ms | 60ms |

( texture.generateMipmaps ist true )

Meine Gedanken

  1. 3x bessere Leistung mit ImageBitmap in Chrome. Sehr schöne Verbesserung.
  2. FireFox hat jetzt ein ImageBitmap-Leistungsproblem? Können Sie es auch an Ihrem Computer versuchen? Auch das Ausprobieren auf dem Handy ist willkommen.
  3. Selbst mit ImageBitmap scheint das Hochladen von Texturen immer noch für große Texturen zu blockieren. Vielleicht brauchen wir inkrementell eine teilweise Upload-Technik oder etwas zum Nichtblockieren.

Selbst mit ImageBitmap scheint das Hochladen von Texturen immer noch für große Texturen zu blockieren. Vielleicht brauchen wir eine teilweise Upload-Technik oder etwas zum Nichtblockieren.

Ich denke, eine Lösung für dieses Problem könnte die Verwendung eines Texturkomprimierungsformats und die Vermeidung von JPG oder PNG (und damit ImageBitmap ) sein. In diesem Zusammenhang wären einige Leistungsdaten interessant.

Ja angenommen. Aber ich denke, wir sehen wahrscheinlich immer noch Blockierungen für große Texturen, insbesondere auf Geräten mit geringem Stromverbrauch wie Mobilgeräten. Wie auch immer, bewerten Sie zuerst die Leistung.

Oder verwenden Sie geplant/requestIdleCallback texSubImage2D

rIC = requestIdleCallback?

Ja, ich habe eine Ninja-Bearbeitung vorgenommen

OK. Ja angenommen.

Übrigens, mit komprimierter Textur kenne ich mich noch nicht aus. Lassen Sie mich mein Verständnis bestätigen. Wir können komprimierte Texturen nicht mit ImageBitmap weil compressedTexImage2D ImageBitmap nicht akzeptiert, richtig?

https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/compressedTexImage2D

Ich ging zurück, um meine alten TiledTextureLoader-Experimente noch einmal zu besuchen - es scheint, als würden sie jetzt meinen Videotreiber zum Absturz und Neustart bringen :(

(Bearbeiten: Tatsächlich sieht es so aus, als ob sogar das Laden der größten Textur (16k x 16k - https://baicoianu.com/~bai/three.js/examples/textures/dotamap1_25.jpg) direkt in Chrome den Absturz verursacht. Dies hat früher gut funktioniert, scheint also eine Regression in der Bildverarbeitung von Chrome zu sein.)

Ich hatte einige Experimente mit requestIdleCallback-, ImageBitmap- und ES6-Generatoren durchgeführt, um eine große Textur zum Hochladen auf die GPU in mehrere Blöcke aufzuteilen. Ich habe einen Framebuffer anstelle einer regulären Textur verwendet, denn selbst wenn Sie texSubimage2D zum Auffüllen der Bilddaten verwenden, müssen Sie den Speicher vorab zuweisen, was das Hochladen einer Reihe leerer Daten auf die GPU erfordert, während ein Framebuffer erstellt werden kann und mit einem einzigen GL-Aufruf initialisiert.

Das Repository für diese Änderungen ist weiterhin hier verfügbar https://github.com/jbaicoianu/THREE.TiledTexture/

Einige Anmerkungen aus meiner Erinnerung an die Experimente:

  • requestIdleCallback hat definitiv dazu beigetragen, den Jank beim Laden von Texturen zu reduzieren, auf Kosten einer erheblichen Verlängerung der Gesamtladezeit
  • Mit etwas zusätzlichem Aufwand könnte dies abgemildert werden, indem man zuerst eine verkleinerte Version der Textur hochlädt und dann die Daten in voller Auflösung in einem gemächlicheren Tempo ausfüllt
  • ES6-Generatoren haben dazu beigetragen, den Code verständlicher zu machen und einfacher zu schreiben, ohne Speicher zu verschwenden, aber dafür sind sie wahrscheinlich nicht wirklich notwendig

Meine Ergebnisse waren ähnlich: Es gab einen Kompromiss zwischen Upload-Geschwindigkeit und Jankiness. (Übrigens habe ich diese https://github.com/spite/THREE.UpdatableTexture erstellt).

Ich denke, dass Sie für die zweite Option, um in WebGL 1 zu arbeiten, tatsächlich zwei Texturen oder zumindest Modifikatoren für die UV-Koordinaten benötigen würden. In WebGL 2 denke ich, dass es einfacher ist, Quellen zu kopieren, die eine andere Größe als die Zieltextur haben.

Ja, mit texSubImage2D denke ich, dass diese Art der Größenänderung nicht möglich wäre, aber wenn ich einen Framebuffer verwende, verwende ich eine OrthographicCamera, um eine Ebene mit dem Texturfragment zu rendern, also ist es nur eine Frage der Skalierung der Ebene für dieser Unentschieden-Aufruf.

Bezüglich des Leistungsproblems von ImageBItmap auf FireFox habe ich einen Fehler auf bugzilla geöffnet

https://bugzilla.mozilla.org/show_bug.cgi?id=1486454

Ich habe versucht, besser zu verstehen, wann die mit einer Textur verbundenen Daten tatsächlich in die GPU geladen werden, und bin auf diesen Thread gestoßen. In meinem speziellen Anwendungsfall bin ich NICHT besorgt über das Laden und Decodieren lokaler JPEG/GIF-Dateien in Texturen, sondern nur über den Versuch, Texturdaten vorab auf die GPU zu laden. Nachdem ich diesen Thread gelesen habe, muss ich gestehen, dass ich mir nicht ganz sicher bin, ob er beide Probleme anspricht oder nur ersteres? Da mir nur letzteres wichtig ist, muss ich nach einer anderen Lösung suchen oder gibt es hier etwas, das dazu beiträgt, das Laden der Texturdaten in die GPU zu erzwingen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen