Three.js: Chargeurs d'actifs non bloquants

Créé le 11 juil. 2017  ·  53Commentaires  ·  Source: mrdoob/three.js

Comme indiqué dans https://github.com/mrdoob/three.js/issues/11301, l' un des principaux problèmes que nous rencontrons dans WebVR, bien qu'il soit également ennuyeux dans les expériences non VR, est de bloquer le thread principal lors du chargement des ressources.

Avec la mise en œuvre récente de la traversée des liens dans le navigateur, le chargement non bloquant est indispensable pour garantir une expérience utilisateur satisfaisante. Si vous passez d'une page à une autre et que la page cible commence à charger des ressources bloquant le fil principal, cela bloquera la fonction de rendu afin qu'aucune image ne soit soumise au casque et après une petite période de grâce, le navigateur nous expulsera de la réalité virtuelle. et il faudra à l'utilisateur de retirer le casque, de cliquer à nouveau sur entrer VR (geste de l'utilisateur requis pour le faire) et de revenir à l'expérience.

Actuellement, nous pouvons voir deux implémentations de chargement non bloquant de fichiers OBJ :

  • (1) Utilisation de webworkers pour analyser le fichier obj, puis renvoyer les données au thread principal WWOBJLoader :
    Ici, l'analyse est effectuée simultanément et vous pouvez avoir plusieurs travailleurs en même temps. Le principal inconvénient est qu'une fois que vous avez chargé les données, vous devez renvoyer la charge utile au thread principal pour reconstruire les TROIS instances d'objets et cette partie pourrait bloquer le thread principal :
    https://github.com/kaisalmen/WWOBJLoader/blob/master/src/loaders/WWOBJLoader2.js#L312 -L423
  • (2) Promesse du thread principal avec analyse différée à l'aide de setTimeOut : Oculus ReactVR : ce chargeur continue de lire les lignes en utilisant de petits intervalles de temps pour éviter de bloquer le thread principal en appelant setTimeout : https://github.com/facebook/react-vr/blob/master /ReactVR/js/Loaders/WavefrontOBJ/OBJParser.js#L281 -L298
    Avec cette approche, le chargement sera plus lent car nous analysons simplement quelques lignes à chaque intervalle de temps, mais l'avantage est qu'une fois l'analyse terminée, nous aurons les TROIS objets prêts à être utilisés sans aucun surcoût supplémentaire.

Les deux ont leurs avantages et leurs inconvénients et je ne suis honnêtement pas un expert des webworkers pour évaluer cette implémentation, mais c'est une discussion intéressante qui conduirait idéalement à un module générique qui pourrait être utilisé pour porter les chargeurs vers une version non bloquante.

Aucune suggestion?

/cc @mikearmstrong001 @kaisalmen @delapuente @spite

Suggestion

Commentaire le plus utile

Version de la première proposition de

Idéalement, cela devrait faire partie de n'importe quelle THREE.Texture, mais j'explorerais d'abord cette approche.

Tous les 53 commentaires

Vous pouvez avoir un chargeur basé sur Promise+worker+incrémental (un peu comme un mélange des deux points)

Transmettez l'URL source au script de travail, récupérez les ressources, renvoyez une structure d' objets transférables avec les tampons requis, les structures, même les ImageBitmaps ; cela devrait être assez simple pour ne pas avoir besoin de beaucoup de temps système de traitement de three.js.

Les données de téléchargement vers le GPU seront malgré tout bloquantes, mais vous pouvez créer une file d'attente pour répartir les commandes sur différentes images, via display.rAF. Les commandes peuvent être exécutées une à la fois par image, ou calculer la durée moyenne de l'opération et en exécuter autant qu'il est "sûr" de les exécuter dans le cadre actuel (quelque chose de similaire à requestIdleCallback serait bien, mais ce n'est pas largement pris en charge , et c'est problématique dans les sessions WebVR). Peut également être amélioré en utilisant bufferSubData, texSubImage2D, etc.

La prise en charge des travailleurs et des objets transférables est assez solide en ce moment, en particulier dans les navigateurs compatibles WebVR.

Bonjour à tous, J'ai un prototype disponible qui pourrait vous intéresser dans ce contexte. Voir la branche suivante :
https://github.com/kaisalmen/WWOBJLoader/tree/Commons
Ici, la partie approvisionnement du maillage a été complètement séparée de WWOBJLoader2 :
https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/WWLoaderCommons.js

WWLoaderCommons facilite l'implémentation d'autres fournisseurs de maillage (chargeurs de format de fichier). Fondamentalement, il définit comment une implémentation de travail Web doit fournir des données de maillage au thread principal et les traiter/intégrer dans la scène. Voir le fournisseur de courrier indésirable triangulaire aléatoire 😉 qui sert de démonstrateur technologique :
https://github.com/kaisalmen/WWOBJLoader/tree/Commons/test/meshspray
https://kaisalmen.de/proto/test/meshspray/main.src.html

Même dans l'implémentation actuelle, WWOBJLoader2 s'appuie sur des objets transférables (ArrayBuffers/ByteBuffers) pour fournir les données brutes BufferedGeometry pour le Mesh du travailleur au thread principal. En termes de temps, la création des Mesh partir des ByteBuffers fournis est négligeable. A chaque fois qu'un plus gros maillage est intégré à la scène, le rendu est bloqué (copies de données, ajustements du graphe de la scène... !?). C'est toujours le cas indépendamment de la source (corrigez-moi si je me trompe).
Le mode "stream" de WWOBJLoader2 lisse ces décrochages, mais si une seule pièce de maillage de votre modèle OBJ pèse 0,5 million de sommets, le rendu s'arrêtera pendant une période plus longue.

J'ai ouvert un nouveau numéro pour détailler ce que j'ai fait exactement sur la branche sur mesure et pourquoi :
https://github.com/kaisalmen/WWOBJLoader/issues/11
Le problème est toujours un bout et les détails suivront bientôt.

Pour offrir quelques chiffres, voici un profil de performance de https://threejs.org/examples/webgl_loader_gltf2.html , chargeant un modèle de 13 Mo avec des textures de 2048x2048.

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

Dans ce cas, la principale chose bloquant le thread principal est de télécharger des textures sur le GPU, et pour autant que je sache, cela ne peut pas être fait à partir d'un WW .. soit le chargeur doit ajouter des textures progressivement, soit three.js doit le gérer en interne.

Pour les curieux, le dernier morceau bloquant le thread principal est l'ajout d'un cubemap d'environnement.

L'objectif principal de react-vr n'est pas nécessairement d'avoir le chargeur le plus optimal en termes de temps d'horloge murale, mais de ne pas provoquer de sorties d'images soudaines et inattendues lors du chargement de nouveau contenu. Tout ce que nous pouvons faire pour minimiser cela est bénéfique pour tous, mais surtout pour la réalité virtuelle.

Les textures sont définitivement un problème et une première étape évidente serait de les charger éventuellement de manière incrémentielle - un ensemble de lignes à la fois pour une grosse texture. Comme le téléchargement est caché pour les programmes clients, il sera difficile pour eux de le gérer, mais je serais tout à fait pour que cela soit plus ouvertement exposé au moteur de rendu webgl pour alléger la pression de three.js

Pour l'analyse gltf, je vois couramment le blocage d'un 500ms sur mes tests, c'est important et je préférerais de loin une approche incrémentale à tous les chargeurs (qui devraient également être clonables)

La prémisse de React VR est d'encourager un contenu dynamique facile basé sur un style Web afin d'encourager davantage de développeurs, ce qui mettra davantage l'accent sur l'amélioration de la gestion dynamique. La plupart du temps, nous ne savons pas quels actifs seront requis au début de nos applications créées par l'utilisateur.

@kaisalmen Merci pour le lien

Dans Elation Engine / JanusWeb, nous effectuons en fait toute notre analyse de modèle à l'aide d'un pool de threads de travail, ce qui fonctionne plutôt bien. Une fois que les travailleurs ont fini de charger chaque modèle, nous le sérialisons à l'aide de object.toJSON() , l'envoyons au thread principal avec postMessage() , puis le chargeons à l'aide de ObjectLoader.parse() . Cela supprime la plupart des parties bloquantes du code du chargeur - il y a encore du temps passé dans ObjectLoader.parse() qui pourrait probablement être optimisé, mais l'interactivité globale et la vitesse de chargement sont considérablement améliorées. Étant donné que nous utilisons un pool de travailleurs, nous pouvons également analyser plusieurs modèles en parallèle, ce qui est une énorme victoire dans les scènes complexes.

Du côté des textures, oui, je pense que certains changements sont nécessaires pour la fonctionnalité de téléchargement de textures de three.js. Un uploader en morceaux utilisant texSubImage2D serait idéal, alors nous pourrions faire des mises à jour partielles de grandes textures sur plusieurs images, comme mentionné ci-dessus.

Je serais plus qu'heureux de collaborer à ce changement, car cela profiterait à de nombreux projets qui utilisent Three.js comme base

Je pense qu'utiliser texSubImage2D est une bonne idée.
Mais aussi pourquoi WebGL ne télécharge pas la texture de manière asynchrone.
OpenGL et d'autres bibliothèques ont la même limitation ?

Et une autre chose à laquelle je pense est la compilation GLSL.
Va-t-il laisser tomber le cadre? Ou assez vite et nous n'avons pas besoin de nous en soucier ?

Oui, c'est également un problème dans OpenGL natif - la compilation de shaders et le téléchargement de données d'image sont des opérations synchrones / bloquantes. C'est pourquoi la plupart des moteurs de jeu recommandent ou même vous obligent à précharger tout le contenu avant de commencer le niveau - il est généralement considéré comme trop pénalisant de charger de nouvelles ressources même à partir d'un disque dur, et ici nous essayons de le faire de manière asynchrone. sur Internet... nous avons en fait un problème plus difficile que la plupart des développeurs de jeux, et nous devrons recourir à des techniques plus avancées si nous voulons pouvoir diffuser du nouveau contenu à la volée.

Le téléchargement de textures sera moins problématique si nous utilisons la nouvelle API ImageBitmap à l'avenir. Voir https://youtu.be/wkDd-x0EkFU?t=82 .

BTW : Grâce à @spite , nous avons déjà un ImageBitmapLoader expérimental dans le projet.

@ Mugen87 en fait, je fais déjà tous mes chargements de texture avec ImageBitmap dans Elation Engine / JanusWeb - cela aide certainement et vaut la peine d'être intégré au noyau Three.js, mais l'utilisation de textures dans WebGL entraîne deux dépenses principales - le temps de décodage de l'image , et le temps de téléchargement de l'image - ImageBitmap n'aide que le premier.

Cela réduit le temps de blocage du processeur d'environ 50% dans mes tests, mais le téléchargement de grandes textures sur le GPU, en particulier 2048x2048 et plus, peut facilement prendre une seconde ou plus.

Il serait pratique d'essayer ce que suggère @jbaicoianu . Quoi qu'il en soit, si vous optez pour l'alternative au thread principal, cela semble correspondre parfaitement à requestIdleCallback au lieu de setTimeout.

Je suis d'accord avec vous tous, je crois que l'approche consiste à charger et à analyser tout sur le travailleur, à créer les objets nécessaires sur le thread principal (si cela coûte très cher, cela pourrait être fait en plusieurs étapes), puis à inclure un chargement incrémentiel sur le moteur de rendu.
Pour un MVP, nous pourrions définir un maxTexturesUploadPerFrame (par défaut infini), et le rendu se chargera du chargement depuis le pool en fonction de ce nombre.
Dans les itérations suivantes, nous pourrions ajouter une logique, comme l' a commenté

requestIdleCallback serait bien, mais ce n'est pas largement pris en charge, et c'est problématique dans les sessions WebVR

@spite Je suis curieux de connaître ta phrase, qu'est-ce que tu veux dire par problématique?

J'ai un THREE.UpdatableTexture pour mettre à jour progressivement les textures à l'aide de texSubImage2D, mais j'ai besoin d'un peu de peaufinage de three.js. L'idée est de préparer un PR pour ajouter du support.

Concernant requestIdleCallback (rIC) :

  • tout d'abord, il est pris en charge sur Chrome et Firefox, et bien qu'il puisse être facilement polyrempli, la version polyremplie peut légèrement contrecarrer l'objectif.

  • Deuxièmement : de la même manière que vrDisplay.requestAnimationFrame (rAF) doit être appelé au lieu de window.rAF lors de la présentation, il en va de même pour rIC, comme indiqué dans ce crbug . Cela signifie que le chargeur doit être au courant de l'affichage actif actuel à tout moment, ou il s'arrêtera de tirer en fonction de ce qui est présenté. Ce n'est pas terriblement compliqué, cela ajoute juste plus de complexité au câblage des chargeurs (qui devraient idéalement faire leur travail, indépendamment de l'état de présentation). Une autre option consiste à avoir la partie dans threejs qui exécute des tâches incrémentielles dans le thread principal pour partager l'affichage actuel ; Je pense que c'est beaucoup plus facile à faire maintenant avec les derniers changements apportés à la réalité virtuelle dans threejs.

Autre considération : afin de pouvoir télécharger une grande texture en plusieurs étapes en utilisant texSubImage2D (256x256 ou 512x512), nous avons besoin d'un contexte WebGL2 pour avoir des fonctionnalités de décalage et de clipping. Sinon, les images doivent être pré-coupées via canvas, essentiellement en mosaïque côté client avant le téléchargement.

@spite Bon point, je n'ai pas pensé au fait que rIC ne soit pas appelé lors de la présentation, au début je pensais que nous devrions avoir besoin d'un display.rIC mais je pense que le .rIC devrait être attaché à la fenêtre et être appelé lorsque la fenêtre ou l'affichage sont tous les deux inactifs.
Je crois que je n'ai rien entendu à ce sujet dans les discussions sur les spécifications de webvr @kearwood a peut-être plus d'informations, mais c'est certainement un problème que nous devrions résoudre.

J'ai hâte de voir votre RP UpdatableTexture ! :) Même s'il ne s'agit que d'un WIP, nous pourrions y déplacer une partie de la discussion.

Peut-être que les chargeurs pourraient devenir quelque chose comme ça...

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 );
        }
    }
} )();

Version de la première proposition de

Idéalement, cela devrait faire partie de n'importe quelle THREE.Texture, mais j'explorerais d'abord cette approche.

@mrdoob, je vois le mérite d'avoir exactement le même code transmis au travailleur, ça me semble tellement faux 😄. Je me demande quel serait l'impact de la sérialisation, du blob et de la réévaluation du script ; rien de trop terrible, mais je ne pense pas que le navigateur soit optimisé pour ces bizarreries 🙃

De plus, idéalement, la récupération de la ressource elle-même se produirait dans le travailleur. Et je pense que la méthode parser() dans le navigateur aurait besoin d'un importScripts de three.js lui-même.

Mais un seul point pour définir les chargeurs sync/async serait génial !

@mrdoob la fonction builder pourrait être complètement générique et commune à tous les chargeurs (WIP : https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL215- LL367 ; Mise à jour : pas encore isolé dans une fonction). Si les données d'entrée sont limitées à des objets js purs sans référence à des objets THREE (c'est ce que vous avez en tête, non ?), nous pourrions créer un code de travail sérialisable sans avoir besoin d'importer dans le travailleur (quel WWOBJLoader fait). C'est facile pour la géométrie, mais les matériaux/ombrages (s'ils sont définis dans le fichier) ne pourraient alors être créés que dans le générateur et uniquement décrits comme JSON auparavant par le parser .
Un travailleur devrait signaler chaque nouveau maillage et son achèvement, je pense. Cela pourrait être modifié comme ceci:

// 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;
},

Un utilitaire de génération de travailleurs est utile + un protocole de communication générique qui ne contredit pas votre idée d'utiliser l'analyseur tel quel, mais il a besoin d'être enveloppé, je pense. État actuel de l'évolution de WWOBJLoader : https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL40 -LL133, alors que les appels frontaux sont report_progress, meshData et complete.

Mise à jour2 :

  • Pensez-vous que l'analyseur devrait être apatride ? C'est ok pour le builder , mais il pourrait être judicieux de pouvoir définir certains paramètres pour ajuster le comportement du parser . Cela implique également que les paramètres de configuration doivent être transférables au travailleur indépendamment de l'analyse.
  • Ce serait cool d'avoir quelque chose comme une fonction run qui mange un objet de configuration générique. Un directeur générique pourrait alors alimenter n'importe quel chargeur avec des instructions (cela fonctionne maintenant dans la branche Commons sur mesure de WWOBJLoader , d'ailleurs)
  • développement en cours : WWOBJLoader2 étend maintenant OBJLoader et remplace l'analyse. Donc, nous avons les deux majuscules d'analyse, mais dans des classes différentes. Elle se rapproche de la proposition, mais elle n'est pas encore conforme. Certains codes d'analyseur doivent être unifiés et éventuellement les deux classes doivent être fusionnées

C'est tout pour le moment. Commentaires bienvenus

@mrdoob J'aime l'idée de composer le travailleur à partir du code du chargeur à la volée. Mon approche actuelle charge simplement l'intégralité de l'application combinée js et utilise simplement un point d'entrée différent du thread principal, ce qui n'est certainement pas aussi efficace que d'avoir des travailleurs composés avec uniquement le code dont ils ont besoin.

J'aime l'approche consistant à utiliser un format de transmission réduit pour le passage entre les travailleurs, car il est facile de marquer ces TypedArrays comme transférables lors du retour au thread principal. Dans mon approche actuelle, j'utilise la méthode .toJSON() dans le worker, mais ensuite je passe en revue et remplace les tableaux JS pour les sommets, les UV, etc. par le type TypedArray approprié et les marque comme transférables lors de l'appel postMessage. Cela rend l'utilisation de l'analyse/de la mémoire un peu plus légère dans le thread principal, au prix d'un peu plus d'utilisation du traitement/de la mémoire dans le travailleur - c'est un bon compromis à faire, mais il pourrait être rendu plus efficace en introduisant un nouveau format de transmission comme vous le proposez, ou en modifiant .toJSON() pour nous donner éventuellement des TypedArrays au lieu des tableaux JS.

Les deux inconvénients que je vois à cette approche simplifiée sont :

  • Nécessite une réécriture des chargeurs de modèles existants. De nombreux chargeurs utilisent TROIS classes et fonctions avec espace de noms intégrés pour effectuer leur tâche, nous aurions donc probablement à extraire un ensemble minimal de code three.js - et avec les travailleurs, cela devient difficile
  • Le format de transmission doit capturer correctement une hiérarchie d'objets, ainsi que différents types d'objets (Mesh, SkinnedMesh, Light, Camera, Object3D, Line, etc.)

@spite Concernant "En outre, idéalement, la récupération de la ressource elle-même se produirait dans le travailleur." - c'est ce que j'ai pensé lorsque j'ai implémenté pour la première fois le chargeur d'actifs basé sur les travailleurs pour Elation Engine - j'avais un pool de 4 ou 8 travailleurs, et je leur transmettais les tâches au fur et à mesure qu'elles devenaient disponibles, puis les travailleurs récupéraient les fichiers, les parsaient eux, et les renvoyer au fil principal. Cependant, dans la pratique, cela signifiait que les téléchargements bloqueraient l'analyse et que vous perdriez les avantages que vous obtiendriez du pipeline, etc. si vous les demandiez tous en même temps.

Une fois que nous avons réalisé cela, nous avons ajouté une autre couche pour gérer tous nos téléchargements d'actifs, puis le téléchargeur d'actifs déclenche des événements pour nous faire savoir quand les actifs deviennent disponibles. Nous les transmettons ensuite au pool de nœuds de calcul, en utilisant des éléments transférables sur les données du fichier binaire pour les faire entrer efficacement dans le nœud de calcul. Avec ce changement, les téléchargements s'effectuent tous plus rapidement même s'ils sont sur le thread principal, et les analyseurs peuvent s'exécuter à plein régime lors du traitement, plutôt que de se tourner les pouces en attendant les données. Dans l'ensemble, cela s'est avéré être l'une des meilleures optimisations que nous ayons faites en termes de vitesse de chargement des actifs.

En ce qui concerne le chargement de texture, j'ai construit une preuve de concept d'une nouvelle classe FramebufferTexture , qui vient avec un compagnon FramebufferTextureLoader . Ce type de texture étend WebGLRenderTarget , et son chargeur peut être configuré pour charger des textures en morceaux de tuiles d'une taille donnée, et les composer dans le framebuffer en utilisant requestIdleCallback() .

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

Dans cet exemple, sélectionnez simplement une taille d'image et une taille de mosaïque et le processus de chargement commencera. Nous initialisons d'abord la texture au rouge pur. Nous commençons le téléchargement des images (elles font environ 10 Mo, alors donnez-lui un peu), et lorsqu'elles sont terminées, nous changeons l'arrière-plan en bleu. À ce stade, nous commençons à analyser l'image avec createImageBitmap() pour analyser le fichier, et lorsque cela est fait, nous configurons un certain nombre de rappels inactifs qui contiennent d'autres appels à createImageBitmap() qui divisent efficacement l'image en tuiles . Ces tuiles sont rendues dans le tampon d'images sur un certain nombre d'images et ont un impact nettement plus faible sur les durées d'image que de tout faire en même temps.

REMARQUE - FireFox ne semble actuellement pas implémenter toutes les versions de createImageBitmap , et me renvoie actuellement une erreur lorsqu'il essaie de se diviser en tuiles. Par conséquent, cette démo ne fonctionne actuellement que dans Chrome. Quelqu'un a-t-il une référence pour une feuille de route de support createImageBitmap dans FireFox ?

Il y a un peu de nettoyage que je dois faire, ce prototype est un peu brouillon, mais je suis très content des résultats et une fois que j'aurai trouvé un moyen de contourner les problèmes entre navigateurs (repli de canevas, etc.), je suis envisagez de l'utiliser comme valeur par défaut pour toutes les textures dans JanusWeb. L'effet de fondu enchaîné est également assez soigné, et nous pourrions même avoir de la fantaisie et créer une version réduite d'abord, puis charger progressivement les tuiles les plus détaillées.

Y a-t-il des raisons liées aux performances ou aux fonctionnalités pour lesquelles quelqu'un peut penser pourquoi il pourrait être une mauvaise idée d'avoir un framebuffer pour chaque texture de la scène, par opposition à une référence de texture standard ? Je n'ai rien trouvé sur max. framebuffers par scène, pour autant que je sache une fois qu'un framebuffer a été configuré, si vous ne le rendez pas, c'est la même chose que toute autre référence de texture, mais j'ai l'impression qu'il me manque quelque chose d'évident quant à pourquoi ce serait une très mauvaise idée :)

@jbaicoianu re: createImageBitmap de firefox, la raison en est qu'ils ne prennent pas en charge le paramètre de dictionnaire, donc il ne prend pas en charge l'orientation de l'image ou la conversion de l'espace colorimétrique. cela rend la plupart des applications de l'API plutôt inutiles. J'ai déposé deux bugs liés au problème : https://bugzilla.mozilla.org/show_bug.cgi?id=1367251 et https://bugzilla.mozilla.org/show_bug.cgi?id=1335594

@malgré c'est ce que je pensais aussi, j'avais vu ce bug à propos de ne pas prendre en charge le dictionnaire d'options - mais dans ce cas, je ne l'utilise même pas, j'essaie juste d'utiliser les options x, y, w, h. L'erreur spécifique que j'obtiens est :

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

Ce qui est déroutant, car je ne vois aucune version de createImageBitmap dans la spécification qui prend un ImageBitmapFormat comme argument.

Y a-t-il des raisons liées aux performances ou aux fonctionnalités pour lesquelles quelqu'un peut penser pourquoi il pourrait être une mauvaise idée d'avoir un framebuffer pour chaque texture de la scène, par opposition à une référence de texture standard ? Je n'ai rien trouvé sur max. framebuffers par scène, pour autant que je sache une fois qu'un framebuffer a été configuré, si vous ne le rendez pas, c'est la même chose que toute autre référence de texture, mais j'ai l'impression qu'il me manque quelque chose d'évident quant à pourquoi ce serait une très mauvaise idée :)

@jbaicoianu THREE.WebGLRenderTarget conserve un framebuffer, une texture et un render buffer. Une fois la texture assemblée, vous pouvez supprimer le framebuffer et le render buffer et ne conserver que la texture. Quelque chose comme ça devrait faire ceci (non testé):

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

@wrr c'est bon à savoir, merci. Je dois certainement faire un pas sur l'efficacité de la mémoire à ce sujet aussi - il se bloque inévitablement à un moment donné si vous modifiez suffisamment les paramètres, donc je sais qu'il y a un nettoyage que je ne fais pas encore. Tout autre indice comme celui-ci serait très apprécié.

@mrdoob et @jbaicoianu J'ai oublié de mentionner que j'aime aussi l'idée. ??
J'ai épuré le code (init retravaillé, objet instructions de travail, gestion des rappels multiples remplacée, description des ressources communes, etc.) de OBJLoader et WWOBJLoader et tous les exemples ( code ). Les deux chargeurs sont maintenant prêts à être combinés. Ils seront selon votre plan, espérons-le, la semaine prochaine en fonction de mon temps libre :
Test WWOBJLoader2 dirigé :
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Utilisateur dirigé du générique WorkerSupport :
https://kaisalmen.de/proto/test/meshspray/main.src.html
Le test du gros fichier OBJ zippé :
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Je mettrai à jour les exemples ci-dessus avec un code plus récent lorsqu'il sera disponible et vous le ferai savoir.
Mise à jour 2017-07-30 : OBJLoader2 et WWOBJLoader2 utilisent désormais des parseurs identiques. Ils transmettent les données à la fonction de constructeur commune directement ou à partir du travailleur.
Mise à jour 2017-07-31 : WWOBJLoader2 est parti. OBJLoader2 fournit parse et parseAsync , load et run (alimenter par LoaderDirector ou manuellement)

Mise à jour 2017-08-09 :
Mise à jour déplacée vers un nouveau message.

OBJLoader2 est à nouveau compatible avec la signature et le comportement avec OBJLoader (je l'ai cassé pendant l'évolution), OBJLoader2 fournit parseAsync et load avec useAsync Drapeau à
https://github.com/kaisalmen/WWOBJLoader/tree/V2.0.0-Beta/src/loaders

J'ai extrait les classes LoaderSupport (indépendantes d'OBJ) qui servent d'utilitaires et d'outils de support requis. Ils pourraient être réutilisés pour d'autres chargeurs potentiels basés sur les travailleurs. Tout le code ci-dessous, je mets sous namespace THREE.LoaderSupport pour mettre en évidence sa dépendance de OBJLoader2 :

  • Builder : Pour la construction générale en treillis
  • WorkerDirector : crée des chargeurs par réflexion, traite PrepData en file d'attente avec le nombre configuré de travailleurs. Utilisé pour automatiser entièrement les chargeurs (démo MeshSpray et Parallels)
  • WorkerSupport : classe utilitaire pour créer des travailleurs à partir du code existant et établir un protocole de communication simple
  • PrepData + ResourceDescriptor : Description utilisée pour l'automatisation ou simplement pour une description unifiée parmi les exemples
  • Commons : Classe de base possible pour les chargeurs (regroupe les paramètres communs)
  • Callbacks : (onProgress, onMeshAlter, onLoad) utilisé pour l'automatisation et la direction et LoadedMeshUserOverride est utilisé pour fournir des informations à partir de onMeshAlter (ajout de normales dans le test objloader2 ci-dessous)
  • Validator : vérifications de variables nulles/indéfinies

@mrdoob @jbaicoianu OBJLoader2 encapsule maintenant un analyseur comme suggéré (il est configuré avec des paramètres définis globalement ou reçus par PrepData pour l'exécution). Le Builder reçoit chaque maillage brut et l'analyseur renvoie le nœud de base, mais à part cela, il correspond au plan.
Il y a encore du code d'aide dans OBJLoader2 pour la sérialisation de l'analyseur qui n'est probablement pas nécessaire.
Le générateur a besoin d'être nettoyé car l'objet contrat/paramètre pour la fonction buildMeshes est toujours fortement influencé par le chargement OBJ et est donc toujours considéré comme en construction.

Le code a besoin d'être peaufiné, mais il est alors prêt pour les commentaires, les discussions, les critiques, etc... 😄

Exemples et tests

OBJ Loader utilisant run et load :
https://kaisalmen.de/proto/test/objloader2/main.src.html
OBJ Loader utilisant run async et parseAsync :
https://kaisalmen.de/proto/test/wwobjloader2/main.src.html
Utilisation dirigée de run async OBJLoader2 :
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Utilisation dirigée de WorkerSupport générique :
https://kaisalmen.de/proto/test/meshspray/main.src.html
Le test du gros fichier OBJ zippé :
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Vous cherchez bien ! Êtes-vous au courant de ces changements dans OBJLoader ? #11871 565c6fd0f3d9b146b9434e5fccfa2345a90a3842

Oui, je dois le porter. J'ai proposé des mesures de perf reproductibles. Je commencerai à travailler sur les deux ce week-end. Quand prévoyez-vous de sortir r87 ? Le support N-gon pourrait le faire en fonction de la date.

@mrdoob et voila: https://github.com/mrdoob/three.js/pull/11928 n-gon support 😄

Mise à jour du statut ( code ):
Les travailleurs créés sont maintenant capables de configurer n'importe quel analyseur à l'intérieur du travailleur via des paramètres reçus par un message. WorkerSupport fournit une implémentation de gestionnaire de travail de référence ( code ) qui peut être complètement remplacée par son propre code si vous le souhaitez ou si cela devient nécessaire.
Le travailleur créera et exécutera l'analyseur dans la méthode run du WorkerRunnerRefImpl ( Parser est disponible dans la portée du travailleur ; this.applyProperties appelle les setters ou les propriétés de l'analyseur syntaxique):

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 );

    }
};

Le message de OBJLoader2.parseAsync ressemble à ceci :

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

L'objet message dépend du chargeur, mais la configuration de l'analyseur dans le travailleur est générique.
Le code utilisé par les exemples liés dans le post précédent a été mis à jour avec le dernier code.

Je pense que l'évolution d'OBJLoader2 et l'extraction des fonctions de support ont maintenant atteint un point où vos retours sont nécessaires. Lorsque tous les exemples ont été portés de son référentiel vers la branche ci-dessus, j'ouvrirai un PR avec un résumé complet, puis je demanderai des commentaires

Pour info, voici un travail en cours pour que ImageBitmapLoader utilise un travailleur comme indiqué ci-dessus. Peut-être plus intéressant encore, quelques chiffres précis sur les résultats : https://github.com/mrdoob/three.js/pull/12456

createImageBitmap de firefox, la raison en est qu'ils ne prennent pas en charge le paramètre de dictionnaire, il ne prend donc pas en charge l'orientation de l'image ou la conversion de l'espace colorimétrique. cela rend la plupart des applications de l'API plutôt inutiles.

C'est malheureux. ☹️

@mrdoob Avez-vous l'intention de passer de ImageLoader à ImageBitmapLoader dans TextureLoader car ImageBitmap devrait être moins bloquant pour le téléchargement vers la texture ? createImageBitmap() semble fonctionner sur FireFox jusqu'à présent si nous ne transmettons que le premier argument. (Peut-être que nous n'avons pas besoin de passer un deuxième argument et plus via TextureLoader ?)

return createImageBitmap( blob );

Il est en fait important que createImageBitmap () supporte le dictionnaire d'options. Sinon, vous ne pouvez pas modifier des éléments tels que l'orientation de l'image (flip-Y) ou indiquer un alpha prémultiplié. Le fait est que vous ne pouvez pas utiliser WebGLRenderingContext.pixelStorei pour ImageBitmap . De la spécification :

_Si le TexImageSource est un ImageBitmap, alors ces trois paramètres (UNPACK_FLIP_Y_WEBGL, UNPACK_PREMULTIPLY_ALPHA_WEBGL, UNPACK_COLORSPACE_CONVERSION_WEBGL) seront ignorés. Au lieu de cela, l'équivalent ImageBitmapOptions doit être utilisé pour créer un ImageBitmap avec le format souhaité._

Je pense donc que nous ne pouvons passer à ImageBitmapLoader si FF prend en charge le dictionnaire d'options. De plus, les propriétés comme Texture.premultiplyAlpha et Texture.flipY ne fonctionnent pas avec ImageBitmap le moment. Je veux dire que si les utilisateurs les définissent, ils n'affecteront pas une texture basée sur ImageBitmap ce qui est quelque peu regrettable.

Ah ok. J'ai raté cette spécification.

L'importance du dictionnaire d'options est également abordée ici :

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

Les bogues sur bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) sont là depuis... deux ans maintenant ? Je ne pensais pas qu'il leur faudrait autant de temps pour le réparer.

Le problème est donc que "techniquement" la fonctionnalité est supportée sur FF, mais en pratique elle est inutile. Pour l'utiliser, nous pourrions avoir un chemin pour Chrome qui l'utilise, et un autre pour les autres navigateurs qui ne l'utilise pas. Le problème est que, puisque Firefox a cette fonctionnalité, nous aurions à faire du reniflement UA, ce qui est nul.

La solution pratique consiste à effectuer une détection de caractéristiques : créez une image 2x2 à l'aide de cIB avec le flip flag, puis relisez et assurez-vous que les valeurs sont correctes.

A propos des bugs de FireFox, je vais aussi les contacter en interne. Voyons si nous avons besoin d'une solution de contournement après avoir entendu leur plan.

Les bogues sur bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) sont là depuis... deux ans maintenant ? Je ne pensais pas qu'il leur faudrait autant de temps pour le réparer.

Oui désolé pour ça, je n'ai vraiment pas suivi pendant un moment -_-

Le problème est donc que "techniquement" la fonctionnalité est supportée sur FF, mais en pratique elle est inutile. Pour l'utiliser, nous pourrions avoir un chemin pour Chrome qui l'utilise, et un autre pour les autres navigateurs qui ne l'utilise pas. Le problème est que, puisque Firefox a cette fonctionnalité, nous aurions à faire du reniflement UA, ce qui est nul.

La solution pratique consiste à effectuer une détection de caractéristiques : créez une image 2x2 à l'aide de cIB avec le flip flag, puis relisez et assurez-vous que les valeurs sont correctes.

Oui, je suis d'accord pour dire que les deux solutions sont vraiment nulles et que nous devrions essayer de les éviter, alors avant de creuser dans l'une d'entre elles, voyons si nous pouvons la débloquer de notre côté.

J'ai fait ImageBitmap test de performance de téléchargement de

Vous pouvez comparer Regular Image vs ImageBitmap.

https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html (Image régulière)
https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html?imagebitmap (ImageBitmap)

Sur mes fenêtres je vois

| Navigateur | 8192x4096 JPG 4,4 Mo | 2048x2048 PNG 4,5 Mo |
| ---- | ---- | ---- |
| Image chromée | 500ms | 140ms |
| Image Bitmap Chrome | 165ms | 35ms |
| Image de FireFox | 500ms | 40ms |
| FireFox ImageBitmap | 500ms | 60ms |

( texture.generateMipmaps est true )

Mes pensées

  1. Performances 3x meilleures avec ImageBitmap sur Chrome. Très belle amélioration.
  2. FireFox a maintenant un problème de performances avec ImageBitmap ? Pouvez-vous aussi essayer sur votre ordinateur ? Essayer sur mobile est également le bienvenu.
  3. Même avec ImageBitmap, le téléchargement de la texture semble toujours bloquer pour les grandes textures. Peut-être avons-nous besoin d'une technique de téléchargement partiel incrémentiel ou de quelque chose pour le non-blocage.

Même avec ImageBitmap, le téléchargement de la texture semble toujours bloquer pour les grandes textures. Peut-être avons-nous besoin d'une technique de téléchargement partiel ou de quelque chose pour le non-blocage.

Je suppose qu'une solution à ce problème pourrait être l'utilisation d'un format de compression de texture et l'évitement de JPG ou PNG (et donc ImageBitmap ). Il serait intéressant de voir quelques données de performance dans ce contexte.

Oui, d'accord. Mais je suppose que nous voyons probablement encore un blocage pour les grandes textures, en particulier sur les appareils à faible consommation comme les mobiles. Quoi qu'il en soit, évaluez d'abord la performance.

Ou utilisez le texSubImage2D programmé/requestIdleCallback

rIC = requestIdleCallback?

oui, j'ai fait un montage ninja

D'ACCORD. Oui d'accord.

BTW, je ne suis pas encore familier avec la texture compressée. Permettez-moi de confirmer ma compréhension. Nous ne pouvons pas utiliser la texture compressée avec ImageBitmap car compressedTexImage2D n'accepte pas ImageBitmap , n'est-ce pas ?

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

Je suis retourné revoir mes anciennes expériences TiledTextureLoader - il semble qu'elles provoquent maintenant le plantage et le redémarrage de mon pilote vidéo :(

(edit: en fait, il semble que même le chargement de la plus grande texture (16k x 16k - https://baicoianu.com/~bai/three.js/examples/textures/dotamap1_25.jpg) directement dans Chrome soit la cause du crash. Cela fonctionnait très bien, il semble donc y avoir une régression dans la gestion des images de Chrome)

J'avais fait quelques expériences en utilisant les générateurs requestIdleCallback, ImageBitmap et ES6 pour diviser une grande texture en plusieurs morceaux à télécharger sur le GPU. J'ai utilisé un framebuffer plutôt qu'une texture ordinaire, car même si vous utilisez texSubimage2D pour remplir les données d'image, vous devez toujours préallouer la mémoire, ce qui nécessite de télécharger un tas de données vides sur le GPU, alors qu'un framebuffer peut être créé et initialisé avec un seul appel GL.

Le référentiel de ces modifications est toujours disponible ici https://github.com/jbaicoianu/THREE.TiledTexture/

Quelques notes de ce dont je me souviens des expériences:

  • requestIdleCallback a certainement aidé à réduire le jank lors du chargement des textures, au détriment d'une augmentation considérable du temps de chargement total
  • Avec un peu de travail supplémentaire, cela pourrait être atténué en téléchargeant d'abord une version réduite de la texture, puis en remplissant les données en pleine résolution à un rythme plus tranquille.
  • Les générateurs ES6 ont aidé à rendre le code plus facile à comprendre et à écrire sans gaspiller de mémoire, mais ne sont probablement pas vraiment nécessaires pour cela

Mes résultats étaient similaires : il y avait un compromis entre la vitesse de téléchargement et la folie. (BTW j'ai créé ce https://github.com/spite/THREE.UpdatableTexture).

Je pense que pour que la deuxième option fonctionne dans WebGL 1, vous auriez en fait besoin de deux textures, ou au moins de modificateurs des coordonnées UV. Dans WebGL 2, je pense qu'il est plus facile de copier des sources de taille différente de la texture cible.

Oui, avec texSubImage2D, je pense que ce genre de redimensionnement ne serait pas possible, mais lors de l'utilisation d'un framebuffer, j'utilise une OrthographicCamera pour rendre un plan avec le fragment de texture, il s'agit donc simplement de changer l'échelle du plan pour cet appel de tirage.

A propos du problème de performances d'ImageBItmap sur FireFox, j'ai ouvert un bogue sur bugzilla

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

J'ai cherché à essayer de mieux comprendre quand les données associées à une texture sont réellement chargées dans le GPU et je suis tombé sur ce fil. Dans mon cas d'utilisation particulier, je ne suis PAS préoccupé par le chargement et le décodage de fichiers jpeg/gif locaux dans des textures, je ne suis préoccupé que par le fait d'essayer de précharger les données de texture sur le GPU. Après avoir lu ce fil, je dois avouer que je ne suis pas tout à fait sûr s'il aborde les deux problèmes ou seulement le premier? Étant donné que je ne me soucie que de ce dernier, dois-je rechercher une solution différente ou y a-t-il quelque chose ici qui aidera à forcer le chargement des données de texture dans le GPU ?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

ghost picture ghost  ·  3Commentaires

jack-jun picture jack-jun  ·  3Commentaires

konijn picture konijn  ·  3Commentaires

Horray picture Horray  ·  3Commentaires

filharvey picture filharvey  ·  3Commentaires