Pixi.js: Wie kann man hohe FPS erzielen, während man viel Text rendert und seine Position aktualisiert?

Erstellt am 25. Sept. 2020  ·  10Kommentare  ·  Quelle: pixijs/pixi.js

Hallo, ich untersuche derzeit PixiJS für ein neues Projekt und es gefällt mir bisher sehr gut! Es gibt einige Anforderungen hinsichtlich der Leistung der Anwendung. Der wirklich schwierige Teil ist, dass dies zu vielen angezeigten Textbeschriftungen führt, die ständig aktualisiert werden. Ich konnte keinen Weg finden, um die Leistung weiter zu verbessern, und meine letzte Hoffnung ist es, Hilfe in dieser Community zu finden.

Tor

1500 Textbeschriftungen
60 Positionsaktualisierungen pro Sekunde
60 FPS

Pixi Spielplatz

Ich habe bereits ein minimales Beispiel mit BitMapText erstellt, das auf dem MacBook Pro ~ 28 FPS ergibt.
https://www.pixiplayground.com/#/edit/rLbAN_xrw7yUU_cg_c8Xv

Haben Sie eine Idee, dies zu verbessern? Vielen Dank im Voraus!

🤔 Question

Hilfreichster Kommentar

Danke, es hat irgendwie funktioniert, eine Textur aus dem Text zu generieren und sie auf einem Sprite zu verwenden. Aber ich verstehe derzeit nicht warum und kann es nicht auf einem Spielplatz reproduzieren. 😄

Die Anwendung aktualisiert jetzt jedoch alle 1500 Etikettenpositionen in jedem Frame mit 60 FPS. Außerdem wird jede Sekunde der Textinhalt aktualisiert. Dies führt jede Sekunde zu einem kurzen Einfrieren. Der nächste Schritt besteht also darin, diese Textaktualisierungen asynchron zu machen, z. B. in einem Webworker. Ich lese gerade über ImageBitmap und OffscreenCanvas. Dies könnte für andere interessant sein, daher werde ich meine Fortschritte teilen.

Alle 10 Kommentare

Sind Sie sicher, dass Sie zu allen Zeiten 1500 auf dem Bildschirm benötigen? Das Ausmerzen von Offscreen-Etiketten trägt wesentlich zur Leistung bei. Pixi macht kein sofortiges Ausmerzen, aber es gibt einige Community-Plugins (z. B. @pixi-essentials/cull von @SukantPal), die Ihnen hier helfen könnten.

Sind alle 1500 Labels einzigartig? Wenn es viele Duplikate gibt, können Sie es mithilfe einer RenderTexture in ein Sprite konvertieren und Referenzen auf Kosten von mehr Speicher freigeben.

@ChristophWalter Das Beispiel, das Sie gezeigt haben, könnte möglicherweise vom Keulen profitieren, wie @bigtimebuddy erwähnt. 1500 Etiketten mit jeweils 112 Zeichen sind eine Menge zu rendern. Es gibt Gründe, warum Ihr Beispiel nicht verwendet werden kann, um eine relevante Optimierung vorzuschlagen. Beispielsweise überlappt sich der Text größtenteils. Ich glaube nicht, dass Ihr Projekt durcheinandergebrachten Text anzeigt, der so dicht ist, dass er vom Benutzer nicht gelesen werden kann.

Das Setzen von MESH.BATCHABLE_SIZE auf einen höheren Wert wie 200 kann hilfreich sein. Ich habe dies nicht gründlich getestet, da es nach einer 6-fachen CPU-Verlangsamung auf meinem iMac immer noch 45 FPS erreicht.

Ich habe das Beispiel nicht zu sehr profiliert. Wenn Sie jedoch an einem kommerziellen Projekt arbeiten und dies sich als GPU-seitiger Engpass herausstellt, können Sie einen Fehler in Betracht ziehen, der jeden Buchstaben (alle As, dann alle Bs, alle Cs usw.) zusammen rendert um die Texturlokalität zu verbessern, eine Kachel-Engine zu entwickeln und / oder eine diff-rect-Optimierung durchzuführen, die den Teil des Bildschirms rendert, der sich geändert hat (dh basierend auf den 60 Aktualisierungen von 1500 Beschriftungen). Dies sind nur Ideen und sollten als solche verstanden werden.

Danke für deine Hilfe!

In der Realität werden wir Culling verwenden und es wird sehr unwahrscheinlich sein, dass so viele Etiketten gleichzeitig angezeigt werden. Aus Benchmark-Gründen müssen wir uns jedoch mit Nicht-Webanwendungen messen. Wir haben diese Anforderungen bereits in Frage gestellt, aber es wäre sehr hilfreich, wenn wir zeigen könnten, dass webgl auch damit umgehen kann.

Die Etiketten sind eindeutig. Aber vielleicht ändern sie sich nicht so oft. Das Beispiel ändert den Textinhalt überhaupt nicht. Es besteht also möglicherweise Optimierungspotenzial. Mir ist aufgefallen, dass die FPS auch dann gleich bleiben, wenn ich die Positionen ( Spielplatz ) nicht aktualisiere. Gibt es eine Möglichkeit, die Texte nur dann neu zu rendern, wenn sie sich ändern?

Das Festlegen von MESH.BATCHABLE_SIZE hat an meiner Seite nichts geändert. Ich werde mir Ihre anderen Ideen ansehen oder empfehlen, iMacs zu kaufen. 😏 Ich bin nicht so begeistert vom Rendern, daher helfen mir diese Ideen wirklich!

Ah, wenn Sie dieses spezielle Beispiel wirklich optimieren, besteht die höchste Methode darin, das zu tun, was @bigtimebuddy gesagt hat, mit einer Änderung: Verwenden Sie ein Netz und aktualisieren Sie dieses Netz direkt (anstatt 1500 Sprites zu verwenden).

Dies bietet zwei Hauptvorteile:

  • Dadurch entfällt der Aufwand für die Pufferphase des Batch-Renderers.
  • Die Anzahl der Scheitelpunkte wird um das 112-fache gekürzt (4 / Text anstelle von 4 / Zeichen). Dadurch werden die Eckpunkte auf nur 6 KB reduziert.
  • Nur ein DisplayObject in der Szene.

Im Grunde würde der Prozess also so aussehen:

  1. Rendern Sie den BitmapText in eine RenderTexture
  2. Erstellen Sie ein Netz mit 1500 * 4 Eckpunkten.
  3. Animieren Sie die Eckpunkte direkt. Sie müssen vorsichtig sein, da es pro Instanz vier Eckpunkte gibt (dh Rechtecke). Sie würden also die Position für den ersten Scheitelpunkt berechnen. Dann wären die anderen drei (x + Breite, Höhe), (x + Breite, y + Höhe), (x, y + Höhe).

Dies sollte ein unkomplizierter Prozess sein. Bitte sagen Sie uns, wie es geht!

Hey, ich habe versucht, deinen Vorschlag umzusetzen. Das Konvertieren des BitmapText in eine Textur schien zu funktionieren. Aber ich stecke gerade fest und versuche, die Textur in einem Netz anzuzeigen. Ich habe versucht, ein PIXI.Mesh und ein PIXI.SimpleMesh zu verwenden. Muss ich meinen eigenen Shader erstellen oder kann ich PIXI.MeshMaterial verwenden?

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_: Es wurde behoben. Die Eckpunkte müssen Pixelkoordinaten darstellen. Nächster Schritt: Verwenden Sie mehr als eine Textur im Netz.

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

- -
_Edit 2_: Dies funktioniert nur, wenn eine Textur für alle Beschriftungen verwendet wird, oder? Ich hätte mich vielleicht falsch ausgedrückt. Die Beschriftungen sind eindeutig, aber der Inhalt ändert sich nicht in jedem Frame.

Mir ist aufgefallen, dass die FPS auch dann gleich bleiben, wenn ich die Positionen ( Spielplatz ) nicht aktualisiere. Gibt es eine Möglichkeit, die Texte nur dann neu zu rendern, wenn sie sich ändern?

Hilft ein PIXI.Mesh dabei? Kennen Sie eine Ressource, in der ich Informationen zum Rendering von PixiJS finden kann?

@ChristophWalter Sie können selbst einen Renderer erstellen (anstatt eine Anwendung zu verwenden) und eine Ticker-Schleife einrichten, die nur Renderer.render _ aufruft, wenn sich Ihre Szene ändert.

Sie können auch autoStart: false an die Anwendungsoptionen übergeben und dann app.render() aufrufen, wenn sich etwas ändert. Gleicher Diff.

Danke, es hat irgendwie funktioniert, eine Textur aus dem Text zu generieren und sie auf einem Sprite zu verwenden. Aber ich verstehe derzeit nicht warum und kann es nicht auf einem Spielplatz reproduzieren. 😄

Die Anwendung aktualisiert jetzt jedoch alle 1500 Etikettenpositionen in jedem Frame mit 60 FPS. Außerdem wird jede Sekunde der Textinhalt aktualisiert. Dies führt jede Sekunde zu einem kurzen Einfrieren. Der nächste Schritt besteht also darin, diese Textaktualisierungen asynchron zu machen, z. B. in einem Webworker. Ich lese gerade über ImageBitmap und OffscreenCanvas. Dies könnte für andere interessant sein, daher werde ich meine Fortschritte teilen.

Kurzes Update, da ich vor einigen Wochen aufgehört habe, Nachforschungen anzustellen
Ich habe den Text in einem Web-Worker gerendert, der eigentlich ganz einfach funktioniert hat:

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

Leider gibt es für meinen Anwendungsfall keine Verbesserung. Die Frames fallen beim Rendern der Texte immer noch ab. Möglicherweise aufgrund der Arbeit, die zum Übertragen der Bild-Bitmaps vom Worker erforderlich ist.

Für unseren speziellen Anwendungsfall können wir empfehlen, die Textlänge jedes Etiketts zu reduzieren, um die Leistungsanforderungen zu erfüllen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen