Pixi.js: Comment atteindre un FPS élevé tout en rendant beaucoup de texte et en mettant à jour sa position?

Créé le 25 sept. 2020  ·  10Commentaires  ·  Source: pixijs/pixi.js

Salut, j'étudie actuellement PixiJS pour un nouveau projet et je l'aime vraiment jusqu'à présent! Il existe certaines exigences concernant les performances de l'application. La partie la plus délicate est que cela entraînera de nombreuses étiquettes de texte affichées qui sont constamment mises à jour. Je n'ai pas pu trouver un moyen d'améliorer encore les performances et mon dernier espoir est de trouver de l'aide dans cette communauté.

Objectif

1500 étiquettes de texte
60 mises à jour de position par seconde
60 images par seconde

Aire de jeux Pixi

J'ai déjà créé un exemple minimal en utilisant BitMapText résultant en ~ 28 FPS sur MacBook Pro.
https://www.pixiplayground.com/#/edit/rLbAN_xrw7yUU_cg_c8Xv

Avez-vous une idée pour améliorer cela? Merci beaucoup d'avance!

🤔 Question

Commentaire le plus utile

Merci, cela a fonctionné d'une manière ou d'une autre en générant une texture à partir du texte et en l'utilisant sur un sprite. Mais je ne comprends actuellement pas pourquoi et je ne peux pas le reproduire dans une aire de jeux. 😄

Cependant, l'application met désormais à jour les 1500 positions d'étiquettes à chaque image avec 60FPS. De plus, chaque seconde, le contenu du texte est mis à jour. Cela conduit à un court gel toutes les secondes. La prochaine étape sera donc de rendre ces mises à jour de texte asynchrones, par exemple dans un web worker. Je lis actuellement sur ImageBitmap et OffscreenCanvas. Cela pourrait être intéressant pour les autres, je vais donc partager mes progrès.

Tous les 10 commentaires

Êtes-vous sûr d'avoir besoin de 1500 à l'écran à _tous_ fois? L'élimination des étiquettes hors écran aidera beaucoup à améliorer les performances. Pixi ne fait aucun tri hors de la boîte, mais il existe quelques plugins communautaires (par exemple, @pixi-essentials/cull par @SukantPal) qui pourraient vous aider ici.

Les 1500 étiquettes sont-elles toutes uniques? S'il y a beaucoup de doublons, vous pouvez le convertir en Sprite à l'aide d'un RenderTexture et partager des références au prix de plus de mémoire.

@ChristophWalter L'exemple que vous avez montré pourrait potentiellement bénéficier de l'abattage, comme @bigtimebuddy l'a mentionné. 1500 étiquettes de 112 caractères chacune, c'est beaucoup à rendre. Il y a des raisons pour lesquelles votre exemple ne peut pas être utilisé pour suggérer une optimisation pertinente, par exemple le texte se chevauche principalement. Je ne pense pas que votre projet affichera du texte brouillé qui est si dense qu'il ne peut pas être lu par l'utilisateur.

Définir MESH.BATCHABLE_SIZE sur une valeur plus élevée telle que 200 peut aider. Je n'ai pas complètement testé cela car il fonctionne toujours à 45 FPS après un ralentissement du processeur 6x sur mon iMac.

Je n'ai pas trop profilé l'exemple. Cependant, si vous travaillez sur un projet commercial et que cela se révèle être un goulot d'étranglement côté GPU, vous pouvez envisager un désordre qui rendra chaque lettre (tous les As, puis tous les Bs, tous les C, etc.) ensemble pour améliorer la localisation des textures, en développant un moteur de tuiles et / ou en faisant une optimisation diff-rect qui rend la partie de l'écran qui a changé (c'est-à-dire basée sur les 60 mises à jour sur 1500 étiquettes). Ce ne sont que des idées et doivent être considérées comme telles.

Merci de votre aide!

En réalité, nous utiliserons l'abattage et il sera très peu probable d'afficher autant d'étiquettes en même temps. Mais pour des raisons de référence, nous devons rivaliser avec les applications non Web. Nous avons déjà remis en question ces exigences, mais cela aiderait beaucoup si nous pouvions montrer que webgl peut également gérer cela.

Les étiquettes seront uniques. Mais ils pourraient ne pas changer si souvent. L'exemple ne change pas du tout le contenu du texte. Il pourrait donc y avoir un potentiel d'optimisation. J'ai remarqué que les FPS restent les mêmes même si je ne mets pas à jour les positions ( terrain de jeu ). Existe-t-il un moyen de restituer les textes uniquement lorsqu'ils changent?

La configuration de MESH.BATCHABLE_SIZE n'a rien changé de mon côté. Je vais jeter un oeil à vos autres idées ou recommander d'acheter des iMacs 😏 Je ne suis pas tellement dans le rendu, donc ces idées m'aident vraiment!

Ah, si vous optimisez vraiment cet exemple spécifique, la méthode suprême serait de faire ce que @bigtimebuddy a dit, avec un changement - utilisez un maillage et mettez à jour ce maillage directement (au lieu d'utiliser 1500 sprites).

Cela offre deux avantages clés:

  • Cela éliminera la surcharge de la phase de mise en mémoire tampon du moteur de rendu par lots.
  • Le nombre de sommets sera coupé de 112x (4 / texte au lieu de 4 / caractère). Cela ramène les sommets à seulement 6K.
  • Un seul DisplayObject dans la scène.

Donc, fondamentalement, le processus ressemblerait à:

  1. Rendre le BitmapText en un RenderTexture
  2. Créez un maillage avec 1500 * 4 sommets.
  3. Animez directement les sommets. Vous devez être prudent car il y a quatre sommets par instance (c'est-à-dire des rectangles). Vous calculeriez donc la position du premier sommet. Ensuite, les trois autres seraient (x + largeur, hauteur), (x + largeur, y + hauteur), (x, y + hauteur).

Cela devrait être un processus simple. Veuillez nous dire comment ça se passe!

Hé, j'ai essayé de mettre en œuvre votre suggestion. La conversion du BitmapText en une texture semblait fonctionner. Mais je suis actuellement bloqué en essayant d'afficher la texture dans un maillage. J'ai essayé d'utiliser un PIXI.Mesh et PIXI.SimpleMesh. Dois-je créer mon propre shader ou puis-je utiliser PIXI.MeshMaterial?

const bitmapFontText = new PIXI.BitmapText(
    'Lorem ipsum dolor\nsit amet consetetur\nsadipscing elitr sed', 
    { font: '55px Desyrel', align: 'left' }
);

const texture =  PIXI.RenderTexture.create({ width: 800, height: 600 });
app.renderer.render(bitmapFontText, texture);

const vertices = [
    -0.5, -0.5,
    0.5, -0.5,
    0.5, 0.5,
    -0.5, 0.5
];
const uvs = [
    0, 0,
    1, 0,
    1, 1,
    0, 1,
];
const indices = [0, 1, 2, 0, 2, 3];
const geometry = new PIXI.MeshGeometry(vertices, uvs, indices);
const shader = new PIXI.MeshMaterial(texture);

const mesh = new PIXI.Mesh(geometry, shader);
app.stage.addChild(mesh);

https://www.pixiplayground.com/#/edit/7RHqFti0tdSzw -6iOtylk

-
_Edit_: corrigé. Les sommets doivent représenter les coordonnées des pixels. _Étape suivante_: utilisez plus d'une texture dans le maillage.

const vertices = [
    0, 0,
    500, 0,
    500, 500,
    0, 500
];

-
_Edit 2_: Cela ne fonctionnera que si vous utilisez une texture pour toutes les étiquettes, non? Je me suis peut-être mal exprimé. Les étiquettes seront uniques, mais le contenu ne changera pas dans chaque cadre.

J'ai remarqué que les FPS restent les mêmes même si je ne mets pas à jour les positions ( terrain de jeu ). Existe-t-il un moyen de restituer les textes uniquement lorsqu'ils changent?

Un PIXI.Mesh vous aidera-t-il? Connaissez-vous une ressource où je peux lire sur le processus de rendu de PixiJS?

@ChristophWalter Vous pouvez créer un moteur de rendu vous-même (au lieu d'utiliser une application), configurer une boucle de ticker qui appelle Renderer.render _only_ lorsque votre scène change.

Vous pouvez également passer autoStart: false aux options de l'application, puis appeler app.render() lorsque quelque chose change. Même diff.

Merci, cela a fonctionné d'une manière ou d'une autre en générant une texture à partir du texte et en l'utilisant sur un sprite. Mais je ne comprends actuellement pas pourquoi et je ne peux pas le reproduire dans une aire de jeux. 😄

Cependant, l'application met désormais à jour les 1500 positions d'étiquettes à chaque image avec 60FPS. De plus, chaque seconde, le contenu du texte est mis à jour. Cela conduit à un court gel toutes les secondes. La prochaine étape sera donc de rendre ces mises à jour de texte asynchrones, par exemple dans un web worker. Je lis actuellement sur ImageBitmap et OffscreenCanvas. Cela pourrait être intéressant pour les autres, je vais donc partager mes progrès.

Brève mise à jour car j'ai arrêté d'enquêter il y a quelques semaines
J'ai rendu le texte dans un web worker qui a en fait fonctionné assez simplement:

// render-text-worker.js
onmessage = function(event) {
    const textLines = event.data.text.split(/\n/);
    const index = event.data.index;
    const offscreen = new OffscreenCanvas(150,45);
    const ctx = offscreen.getContext("2d");
    ctx.font = "15px monospace";
    textLines.forEach((text, index) => {
        ctx.fillText(text, 0, 15 + index * 15)
    })
    const imageBitmap = offscreen.transferToImageBitmap();
    postMessage({imageBitmap, index});
};
// index.js
const worker = new Worker("render-text-worker.js");
const callbacks ={}
function getLabelTexture(index, text, callback) {
  callbacks[index] = callback
  worker.postMessage({ index, text });
}
worker.onmessage = function({ data }) {
  const callback = callbacks[data.index]
  callback(PIXI.Texture.from(data.imageBitmap));
}

Malheureusement, il n'y a aucune amélioration pour mon cas d'utilisation. Les cadres tombent toujours lors du rendu des textes. Peut-être en raison du travail requis pour transférer les images bitmap du travailleur.

Pour notre cas d'utilisation spécifique, nous sommes en mesure de recommander de réduire la longueur du texte de chaque étiquette pour atteindre les exigences de performance.

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