Pixi.js: ¿Cómo lograr un FPS alto mientras se renderiza mucho texto y se actualiza su posición?

Creado en 25 sept. 2020  ·  10Comentarios  ·  Fuente: pixijs/pixi.js

Hola, actualmente estoy investigando PixiJS para un nuevo proyecto y ¡realmente me gusta hasta ahora! Existen algunos requisitos relacionados con el rendimiento de la aplicación. La parte realmente complicada es que esto dará como resultado muchas etiquetas de texto mostradas que se actualizan constantemente. No pude encontrar una manera de mejorar aún más el rendimiento y mi última esperanza es encontrar ayuda en esta comunidad.

Objetivo

1500 etiquetas de texto
60 actualizaciones de posición por segundo
60 FPS

Parque infantil Pixi

Ya creé un ejemplo mínimo usando BitMapText que resultó en ~ 28 FPS en MacBook Pro.
https://www.pixiplayground.com/#/edit/rLbAN_xrw7yUU_cg_c8Xv

¿Tienes alguna idea para mejorar esto? ¡Muchas gracias por adelantado!

🤔 Question

Comentario más útil

Gracias, de alguna manera funcionó generando una textura a partir del texto y usándola en un objeto. Pero actualmente no entiendo por qué y no puedo reproducirlo en un patio de recreo. 😄

Sin embargo, la aplicación ahora actualiza todas las 1500 posiciones de etiquetas en cada cuadro con 60FPS. Además, cada segundo se actualiza el contenido del texto. Esto conduce a una breve congelación cada segundo. Así que el siguiente paso será convertir esas actualizaciones de texto en asincrónicas, por ejemplo, en un trabajador web. Actualmente estoy leyendo sobre ImageBitmap y OffscreenCanvas. Esto puede ser interesante para otros, así que compartiré mi progreso.

Todos 10 comentarios

¿Estás seguro de que necesitas 1500 en pantalla en todo momento? Eliminar etiquetas fuera de la pantalla ayudará mucho con el rendimiento. Pixi no realiza ninguna selección de fábrica, pero hay algunos complementos de la comunidad (por ejemplo, @pixi-essentials/cull de @SukantPal) que pueden ayudarlo aquí.

¿Son las 1500 etiquetas únicas? Si hay muchos duplicados, puede convertirlo en un Sprite usando RenderTexture y compartir referencias al costo de más memoria.

@ChristophWalter El ejemplo que ha mostrado podría beneficiarse potencialmente de la selección como mencionó @bigtimebuddy . 1500 etiquetas cada una con 112 caracteres es _mucho_ para representar. Hay razones por las que su ejemplo no puede usarse para sugerir una optimización relevante, por ejemplo, el texto se superpone principalmente. No creo que su proyecto muestre texto desordenado que sea tan denso que el usuario no pueda leerlo.

Establecer MESH.BATCHABLE_SIZE en un valor más alto como 200 puede ayudar. No probé esto a fondo porque todavía funciona a 45 FPS después de una desaceleración de la CPU 6x en mi iMac.

No he perfilado demasiado el ejemplo. Sin embargo, si está trabajando en un proyecto comercial y esto resulta ser un cuello de botella del lado de la GPU, podría considerar un desorden que representa cada letra (todas las A, luego todas las B, todas las C, etc.) juntas para mejorar la ubicación de la textura, desarrollando un motor de mosaico y / o haciendo una optimización de rectificación diferencial que muestre la parte de la pantalla que ha _cambiado_ (es decir, basándose en las 60 actualizaciones de 1500 etiquetas). Estas son solo ideas y deben tomarse como tales.

¡Gracias por tu ayuda!

En realidad, utilizaremos la selección y será muy poco probable que se muestren tantas etiquetas al mismo tiempo. Pero por razones de referencia, debemos competir con aplicaciones que no son web. Ya cuestionamos estos requisitos, pero ayudaría mucho si pudiéramos demostrar que webgl también puede manejar esto.

Las etiquetas serán únicas. Pero es posible que no cambien con tanta frecuencia. El ejemplo no cambia el contenido del texto en absoluto. Por lo tanto, podría haber cierto potencial para optimizar. Noté que el FPS permanece igual incluso si no actualizo las posiciones (área de juegos ). ¿Hay alguna forma de volver a reproducir los textos solo cuando cambian?

Establecer MESH.BATCHABLE_SIZE no cambió nada de mi lado. Echaré un vistazo a tus otras ideas o te recomendaré comprar iMacs 😏 No me gusta mucho el renderizado, ¡así que estas ideas realmente me ayudan!

Ah, si realmente está optimizando ese ejemplo específico, entonces el método supremo sería hacer lo que dijo @bigtimebuddy , con un cambio: use una malla y actualice esa malla directamente (en lugar de usar 1500 sprites).

Esto proporciona dos beneficios clave:

  • Esto eliminará la sobrecarga de la fase de almacenamiento en búfer del renderizador por lotes.
  • El número de vértices se reducirá 112x (4 / texto en lugar de 4 / carácter). Eso reduce los vértices a solo 6K.
  • Solo un DisplayObject en la escena.

Entonces, básicamente, el proceso se vería así:

  1. Renderizar BitmapText en una RenderTexture
  2. Crea una malla con 1500 * 4 vértices.
  3. Anime los vértices directamente. Debe tener cuidado porque hay cuatro vértices por instancia (es decir, rectángulos). Entonces calcularías la posición del primer vértice. Entonces los otros tres serían (x + ancho, alto), (x + ancho, y + alto), (x, y + alto).

Este debería ser un proceso sencillo. ¡Cuéntanos cómo te fue!

Oye, intenté implementar tu sugerencia. La conversión de BitmapText en una textura pareció funcionar. Pero actualmente estoy atascado tratando de mostrar la textura en una malla. Intenté usar PIXI.Mesh y PIXI.SimpleMesh. ¿Necesito crear mi propio sombreador o puedo usar 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

-
_Editar_: Arreglado. Los vértices deben representar coordenadas de píxeles. _Siguiente paso_: Usa más de una textura en la malla.

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

-
_Edit 2_: Esto solo funcionará cuando se use una textura para todas las etiquetas, ¿verdad? Podría haberme expresado mal. Las etiquetas serán únicas, pero el contenido no cambiará en cada fotograma.

Noté que el FPS permanece igual incluso si no actualizo las posiciones (área de juegos ). ¿Hay alguna forma de volver a reproducir los textos solo cuando cambian?

¿Ayudará un PIXI.Mesh con esto? ¿Conoce algún recurso donde pueda leer sobre el proceso de renderizado de PixiJS?

@ChristophWalter Puede crear un renderizador usted mismo (en lugar de usar una Aplicación), configurar un bucle de ticker que llame a Renderer.render _only_ cuando cambie su escena.

También puede pasar autoStart: false a las opciones de la Aplicación y luego llamar a app.render() cuando algo cambie. Misma diferencia

Gracias, de alguna manera funcionó generando una textura a partir del texto y usándola en un objeto. Pero actualmente no entiendo por qué y no puedo reproducirlo en un patio de recreo. 😄

Sin embargo, la aplicación ahora actualiza todas las 1500 posiciones de etiquetas en cada cuadro con 60FPS. Además, cada segundo se actualiza el contenido del texto. Esto conduce a una breve congelación cada segundo. Así que el siguiente paso será convertir esas actualizaciones de texto en asincrónicas, por ejemplo, en un trabajador web. Actualmente estoy leyendo sobre ImageBitmap y OffscreenCanvas. Esto puede ser interesante para otros, así que compartiré mi progreso.

Breve actualización ya que dejé de investigar hace algunas semanas.
Representé el texto en un trabajador web que en realidad funcionó bastante sencillo:

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

Desafortunadamente, no hay ninguna mejora para mi caso de uso. Los fotogramas siguen cayendo mientras se renderizan los textos. Quizás debido al trabajo requerido para transferir los mapas de bits de la imagen del trabajador.

Para nuestro caso de uso específico, podemos recomendar reducir la longitud del texto de cada etiqueta para lograr los requisitos de rendimiento.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

lucap86 picture lucap86  ·  3Comentarios

Makio64 picture Makio64  ·  3Comentarios

MRVDH picture MRVDH  ·  3Comentarios

lunabunn picture lunabunn  ·  3Comentarios

madroneropaulo picture madroneropaulo  ·  3Comentarios