Descripción general de la arquitectura de RenderingNG

Chris Harrelson
Chris Harrelson

En una entrada anterior, presenté una descripción general de los objetivos de la arquitectura de RenderingNG y las propiedades clave. En esta publicación, se explicará cómo se configuran sus componentes y cómo fluye a través de ellos la canalización de renderización.

Comenzando en el nivel más alto y luego avanzando desde allí, las tareas de renderización son las siguientes:

  1. Renderiza contenido en píxeles en la pantalla.
  2. Anima efectos visuales en el contenido de un estado a otro.
  3. Desplazarse en respuesta a la entrada.
  4. Enruta las entradas de forma eficiente a los lugares correctos para que las secuencias de comandos del desarrollador y otros subsistemas puedan responder.

El contenido que se renderizará es un árbol de marcos para cada pestaña del navegador, además de la IU del navegador. además de un flujo de eventos de entrada sin procesar desde pantallas táctiles, mouse, teclados y otros dispositivos de hardware.

Cada marco incluye lo siguiente:

  • Estado del DOM
  • CSS
  • Lienzos
  • Recursos externos, como imágenes, videos, fuentes y SVG

Un marco es un documento HTML, además de su URL. Una página web cargada en una pestaña del navegador tiene un marco de nivel superior, marcos secundarios para cada iframe incluido en el documento de nivel superior y sus subordinados recurrentes de iframe.

Un efecto visual es una operación gráfica que se aplica a un mapa de bits, como desplazar, transformar, recortar, filtrar, aplicar opacidad o mezclar.

Componentes de la arquitectura

En RenderingNG, estas tareas se dividen lógicamente en varias etapas y componentes de código. Los componentes terminan en varios procesos de CPU, subprocesos y subcomponentes dentro de esos subprocesos. Cada uno desempeña un papel importante a la hora de lograr confiabilidad, rendimiento escalable y extensibilidad para todo el contenido web.

Renderización de la estructura de la canalización

Diagrama de la canalización de renderización como se explica en el siguiente texto.

La renderización se realiza en una canalización con una serie de etapas y artefactos creados a lo largo del proceso. Cada etapa representa el código que realiza una tarea bien definida dentro de la renderización. Los artefactos son estructuras de datos que son entradas o salidas de las etapas. En el diagrama, las entradas o salidas se indican con flechas.

En esta entrada de blog, no se brindarán muchos detalles sobre los artefactos, lo que se analizará en la próxima entrada: Estructuras de datos clave y sus funciones en RenderingNG.

Las etapas de la canalización

En el diagrama anterior, las etapas tienen notas con colores que indican en qué subproceso o proceso se ejecutan:

  • Verde: Subproceso principal del proceso de renderización
  • Amarillo: compositor del proceso de renderización
  • Naranja: Proceso de visualización

En algunos casos, pueden ejecutarse en varios lugares, según las circunstancias, motivo por el cual algunos tienen dos colores.

Las etapas son:

  1. Animación: Cambia los estilos calculados y muta los árboles de propiedades con el tiempo según cronogramas declarativos.
  2. Estilo: Aplica CSS al DOM y crea estilos computados.
  3. Diseño: Determina el tamaño y la posición de los elementos del DOM en la pantalla y crea el árbol de fragmentos inmutable.
  4. Pintura previa: calcula los árboles de propiedades y invalidate las listas de visualización y los mosaicos de textura de GPU existentes según corresponda.
  5. Scroll: Actualiza el desplazamiento de los documentos y los elementos del DOM desplazables mediante la mutación de los árboles de propiedades.
  6. Paint: Calcula una lista de visualización que describe cómo generar tramas de mosaicos de texturas de la GPU desde el DOM.
  7. Confirmación: Copia los árboles de propiedades y la lista de visualización en el subproceso del compositor.
  8. Creación de capas: Divide la lista de visualización en una lista de capas compuestas para lograr una rasterización y una animación independientes.
  9. Trabajadores de trama, decodificación y pintura: Convierte las listas de visualización, las imágenes codificadas y el código del worklet de pintura, respectivamente, en mosaicos de textura de GPU.
  10. Activar: Crea un marco compositor que represente la forma de dibujar y posicionar mosaicos de GPU en la pantalla, junto con efectos visuales.
  11. Aggregate: Combina los fotogramas visibles del compositor en un solo marco global del compositor.
  12. Draw: Ejecuta el fotograma agregado del compositor en la GPU para crear píxeles en pantalla.

Las etapas de la canalización de renderización se pueden omitir si no son necesarias. Por ejemplo, las animaciones de efectos visuales y los desplazamientos pueden omitir el diseño, la pintura previa y la pintura. Es por eso que la animación y el desplazamiento están marcados con puntos amarillos y verdes en el diagrama. Si se pueden omitir el diseño, la pintura previa y la pintura para los efectos visuales, estos se pueden ejecutar por completo en el subproceso del compositor y omitir el subproceso principal.

La renderización de la IU del navegador no se representa directamente aquí, pero se puede considerar como una versión simplificada de esta misma canalización (y, de hecho, su implementación comparte gran parte del código). Por lo general, el video (que no se representa directamente) se procesa mediante un código independiente que decodifica los fotogramas en mosaicos de texturas de la GPU que, luego, se conectan a los marcos del compositor y al paso de dibujo.

Estructura del proceso y del subproceso

Procesos de la CPU

El uso de varios procesos de CPU logra aislamiento de rendimiento y seguridad entre los sitios y del estado del navegador, y ofrece estabilidad y aislamiento de seguridad del hardware de la GPU.

Diagrama de las distintas partes de los procesos de la CPU

  • El proceso de renderización renderiza, anima, desplaza y enruta las entradas para una sola combinación de sitio y pestaña. Existen muchos procesos de renderización.
  • El proceso del navegador renderiza, anima y enruta la entrada para la IU del navegador (incluida la barra de URL, los títulos de las pestañas y los íconos) y enruta todas las entradas restantes al proceso de renderización adecuado. Hay exactamente un proceso de navegación.
  • El proceso de visualización agrega la composición de varios procesos de renderización y el proceso del navegador. Traza y dibuja con la GPU. Hay exactamente un proceso de Viz.

Los diferentes sitios siempre terminan en procesos de renderización diferentes. (en realidad, siempre puedes hacerlo en computadoras de escritorio, siempre que sea posible en dispositivos móviles. Escribiré "siempre" a continuación, pero esta salvedad se aplica en todo momento).

Varias pestañas del navegador o ventanas del mismo sitio suelen ir en diferentes procesos de renderización, a menos que las pestañas estén relacionadas (una abre la otra). Con mucha presión de memoria en computadoras de escritorio, Chromium puede colocar varias pestañas del mismo sitio en el mismo proceso de procesamiento, incluso si no están relacionadas.

En una sola pestaña del navegador, los marcos de diferentes sitios siempre están en procesos de renderización diferentes entre sí, pero los marcos del mismo sitio siempre están en el mismo proceso de renderización. Desde la perspectiva del procesamiento, la ventaja importante de varios procesos de renderización es que las pestañas y los iframes entre sitios logran un aislamiento de rendimiento entre sí. Además, los orígenes pueden habilitar aún más aislamiento.

Existe exactamente un proceso de Viz para todo Chromium. Después de todo, generalmente hay una sola GPU y pantalla para dibujar. Separar Viz en su propio proceso es útil para lograr la estabilidad ante errores en los controladores o el hardware de las GPU. También es útil para el aislamiento de seguridad, que es importante para las APIs de GPU como Vulkan. También es importante para la seguridad en general.

Dado que el navegador puede tener muchas pestañas y ventanas, y todas tienen píxeles de la IU del navegador para dibujar, es posible que te preguntes: ¿por qué hay exactamente un proceso del navegador? El motivo es que solo una de ellas está enfocada a la vez; de hecho, las pestañas del navegador no visibles se desactivan en su mayoría y descartan toda su memoria GPU. Sin embargo, las funciones complejas de renderización de la IU del navegador también se implementan cada vez más en los procesos de renderización (conocidos como WebUI). Esto no es por motivos de aislamiento del rendimiento, sino para aprovechar la facilidad de uso del motor de renderización web de Chromium.

En dispositivos Android más antiguos, el proceso de renderización y del navegador se comparten cuando se usan en un WebView (esto no se aplica a Chromium en Android en general, solo a WebView). En WebView, el proceso del navegador también se comparte con la app de incorporación, y WebView solo tiene un proceso de renderización.

A veces, también existe un proceso de utilidad para decodificar contenido de video protegido. Este proceso no se describe más arriba.

Conversaciones

Los subprocesos ayudan a lograr el aislamiento del rendimiento y la capacidad de respuesta a pesar de las tareas lentas, la paralelización de canalizaciones y el almacenamiento en búfer múltiple.

Diagrama del proceso de renderización como se describe en el artículo.

  • El subproceso principal ejecuta secuencias de comandos, el bucle de eventos de renderización, el ciclo de vida del documento, las pruebas de posicionamiento, el envío de eventos de secuencias de comandos y el análisis de HTML, CSS y otros formatos de datos.
    • Los auxiliares de subprocesos principales realizan tareas como crear mapas de bits de imágenes y BLOB que requieren codificación o decodificación.
    • Los Web Workers ejecutan una secuencia de comandos y un bucle de eventos de renderización para OffscreenCanvas.
  • El subproceso compositor procesa eventos de entrada, realiza desplazamientos y animaciones del contenido web, calcula la asignación óptima de capas del contenido web y coordina la decodificación de imágenes, los trabajos de pintura y las tareas de trama.
    • Los auxiliares de subprocesos de compositor coordinan las tareas de trama de Viz y ejecutan tareas de decodificación de imágenes, trabajos de pintura y trama de resguardo.
  • Subprocesos de salida multimedia, demuxer o salida de audio decodifican, procesan y sincronizan transmisiones de audio y video. (Recuerda que el video se ejecuta en paralelo con la canalización de renderización principal).

Separar los subprocesos principales del compositor es fundamental para el aislamiento del rendimiento de la animación y el desplazamiento del trabajo del subproceso principal.

Solo hay un subproceso principal por proceso de renderización, aunque varias pestañas o marcos del mismo sitio puedan terminar en el mismo proceso. Sin embargo, hay aislamiento de rendimiento del trabajo realizado en varias APIs de navegador. Por ejemplo, la generación de mapas de bits y BLOB de imágenes en la API de Canvas se ejecuta en un subproceso de ayuda de subprocesos principal.

Del mismo modo, solo hay un subproceso del compositor por proceso de renderización. Por lo general, no es un problema que solo haya una, ya que todas las operaciones realmente costosas en el subproceso del compositor se delegan a los subprocesos de trabajo del compositor o al proceso de Viz, y este trabajo se puede realizar en paralelo con el enrutamiento de entrada, el desplazamiento o la animación. Las tareas de coordinación de los subprocesos de trabajo del compositor se ejecutan en el proceso de Viz, pero la aceleración de GPU en todas partes puede fallar por motivos ajenos al control de Chromium, como errores del controlador. En estas situaciones, el subproceso de trabajo realizará el trabajo en un modo de resguardo en la CPU.

La cantidad de subprocesos de trabajo del compositor depende de las capacidades del dispositivo. Por ejemplo, las computadoras de escritorio suelen usar más subprocesos, ya que tienen más núcleos de CPU y tienen menos restricciones de batería que los dispositivos móviles. Este es un ejemplo de cómo aumentar y reducir la escala.

También es interesante tener en cuenta que la arquitectura de subprocesos del proceso de renderización es una aplicación de tres patrones de optimización diferentes:

  • Subprocesos de ayuda: Enviar subtareas de larga duración a subprocesos adicionales para que el subproceso superior responda a otras solicitudes que se realizan simultáneamente. Los subprocesos de ayuda principal de subprocesos y los del compositor son buenos ejemplos de esta técnica.
  • Almacenamiento en búfer múltiple: Se muestra el contenido renderizado anteriormente mientras se renderiza contenido nuevo para ocultar la latencia de renderización. El subproceso del compositor usa esta técnica.
  • Paralelización de canalizaciones: Ejecución de la canalización de procesamiento en varios lugares de forma simultánea. Así es como el desplazamiento y la animación pueden ser rápidos, incluso si se lleva a cabo una actualización de renderización del subproceso principal, ya que el desplazamiento y la animación se pueden ejecutar en paralelo.

Proceso del navegador

Diagrama del proceso del navegador que muestra la relación entre el subproceso de renderización y el de composición, y el auxiliar de subprocesos de renderización y composición.

  • El subproceso de renderización y composición responde a la entrada en la IU del navegador, enruta otras entradas al proceso de renderización correcto, y diseña y pinta la IU del navegador.
  • Los auxiliares de subprocesos de renderización y composición ejecutan tareas de decodificación de imágenes y la trama o decodificación de resguardo.

El procesamiento del proceso del navegador y el subproceso compuesto son similares al código y la funcionalidad de un proceso de renderización, excepto que el subproceso principal y el subproceso compositor se combinan en uno. En este caso, solo se necesita un subproceso, ya que no es necesario aislar el rendimiento de las tareas largas del subproceso principal, ya que no hay ninguna por diseño.

Proceso de visualización

Un diagrama que muestra que el proceso de Viz incluye el subproceso principal de la GPU y el subproceso del compositor de pantalla.

  • Las tramas del subproceso principal de la GPU muestran listas y fotogramas de video en mosaicos de textura de GPU y dibuja fotogramas del compositor en la pantalla.
  • El subproceso del compositor de pantallas agrega y optimiza la composición de cada proceso de renderización, además del proceso del navegador, en un solo marco del compositor para presentar en la pantalla.

Las tramas y el dibujo suelen realizarse en el mismo subproceso, ya que ambos dependen de los recursos de la GPU y es difícil hacer un uso de la GPU con varios subprocesos de manera confiable (un acceso multiproceso más sencillo a la GPU es una de las motivaciones para desarrollar el nuevo estándar de Vulkan). En WebView de Android, hay un subproceso de renderización independiente a nivel del SO para dibujar debido a la forma en que las WebViews están incorporadas en una app nativa. Es probable que otras plataformas tengan un subproceso de este tipo en el futuro.

El compositor de pantalla está en un subproceso diferente porque debe ser responsivo en todo momento y no bloquear ninguna fuente posible de demora en el subproceso principal de la GPU. Una causa de demora en el subproceso principal de la GPU son las llamadas a código que no es de Chromium, como los controladores de GPU específicos de proveedores, que pueden ser lentas de maneras difíciles de predecir.

Estructura de los componentes

Dentro de cada subproceso principal o compositor del proceso de renderización, hay componentes de software lógicos que interactúan entre sí de formas estructuradas.

Cómo renderizar los componentes del subproceso principal del proceso de renderización

Diagrama del procesador Blink.

  • Procesador de intermitencia:
    • El fragmento de árbol de marcos local representa el árbol de marcos locales y el DOM dentro de los marcos.
    • El componente de las APIs de DOM y Canvas contiene implementaciones de todas estas APIs.
    • El ejecutor del ciclo de vida del documento ejecuta los pasos de la canalización de renderización, incluido el paso de confirmación.
    • El componente de prueba y despacho de eventos de entrada ejecuta pruebas de posicionamiento para averiguar a qué elemento DOM se orienta un evento y ejecuta los algoritmos de despacho y los comportamientos predeterminados del evento de entrada.
  • El programador y ejecutor de bucles de eventos de renderización decide qué ejecutar en el bucle de eventos y cuándo. Programa la renderización para que se realice a una cadencia que coincida con la pantalla del dispositivo.

Diagrama del árbol de marcos

Los fragmentos de árbol de marcos locales son un poco complicados. Recuerda que un árbol de marcos es la página principal y sus iframes secundarios, de manera recursiva. Un marco es local para un proceso de renderización si se renderiza en ese proceso; de lo contrario, es remoto.

Puedes imaginarte colores de fotogramas según su proceso de renderización. En la imagen anterior, los círculos verdes son todos los marcos en un proceso de renderización; los naranjas están en un segundo y el azul está en un tercero.

Un fragmento de árbol de marcos local es un componente conectado del mismo color en un árbol de marcos. Hay cuatro árboles de marcos locales en la imagen: dos para el sitio A, uno para el sitio B y otro para el sitio C. Cada árbol de marcos local tiene su propio componente de renderizador Blink. El procesador Blink de un árbol de fotogramas local puede o no estar en el mismo proceso de renderización que otros árboles de fotogramas locales (esto se determina según la forma en que se seleccionan los procesos de renderización, como se describió antes).

Estructura de subprocesos del compositor del proceso de renderización

Diagrama que muestra los componentes del compositor del proceso de renderización.

Entre los componentes del compositor del proceso de renderización se incluyen los siguientes:

  • Es un controlador de datos que mantiene una lista de capas compuestas, listas de visualización y árboles de propiedades.
  • Un ejecutor de ciclo de vida que ejecuta los pasos de animación, desplazamiento, composición, trama, decodificación y activación de la canalización de renderización. (Recuerda que la animación y el desplazamiento pueden ocurrir tanto en el subproceso principal como en el compositor).
  • Un controlador de entrada y prueba de posicionamiento realiza el procesamiento de entrada y las pruebas de posicionamiento en la resolución de las capas compuestas para determinar si los gestos de desplazamiento se pueden ejecutar en el subproceso del compositor y qué tipo de pruebas de posicionamiento del proceso de renderización deben orientarse.

Ejemplo en la práctica

Hagamos que la arquitectura sea concreta con un ejemplo. En este ejemplo, hay tres pestañas:

Pestaña 1: foo.com

<html>
  <iframe id=one src="foo.com/other-url"></iframe>
  <iframe  id=two src="bar.com"></iframe>
</html>

Pestaña 2: bar.com

<html>
 …
</html>

Pestaña 3: baz.com html <html> … </html>

La estructura del proceso, el subproceso y los componentes de estas pestañas se verá de la siguiente manera:

Diagrama del proceso para las pestañas.

Ahora, veamos un ejemplo de cada una de las cuatro tareas principales de renderización, que, como recordarás, son las siguientes:

  1. Renderiza el contenido en píxeles en la pantalla.
  2. Anima efectos visuales sobre el contenido de un estado a otro.
  3. Desplazarse en respuesta a la entrada.
  4. Enruta las entradas de forma eficiente a los lugares correctos para que las secuencias de comandos del desarrollador y otros subsistemas puedan responder.

Para render el DOM modificado para la pestaña uno, sigue estos pasos:

  1. Una secuencia de comandos del desarrollador cambia el DOM en el proceso de renderización de foo.com.
  2. El procesador Blink le indica al compositor que necesita una renderización.
  3. El compositor le indica a Viz que necesita una renderización para que ocurra.
  4. Viz le indica al compositor el inicio de la renderización.
  5. El compositor reenvía la señal de inicio al renderizador de Blink.
  6. El ejecutor de bucles de eventos de subproceso principal ejecuta el ciclo de vida del documento.
  7. El subproceso principal envía el resultado al subproceso del compositor.
  8. El ejecutor de bucles de eventos del compositor ejecuta el ciclo de vida de composición.
  9. Todas las tareas de trama se envían a Viz para estas tareas (a menudo hay más de una de estas tareas).
  10. Viz procesa contenido en la GPU.
  11. Viz confirma que se completó la tarea de trama. Nota: Con frecuencia, Chromium no espera a que se complete la trama y, en su lugar, utiliza un token de sincronización que debe resolverse con tareas de trama antes de que se ejecute el paso 15.
  12. Se envía un fotograma del compositor a Viz.
  13. Viz agrega los marcos del compositor para el proceso de renderización de foo.com, el proceso de renderización de iframe bar.com y la IU del navegador.
  14. Viz programa un sorteo.
  15. Viz dibuja el marco agregado del compositor en la pantalla.

Para animar una transición de transformación CSS en la pestaña dos, sigue estos pasos:

  1. El subproceso del compositor para el proceso de renderización de bar.com marca una animación en su bucle de eventos del compositor mediante la mutación de los árboles de propiedades existentes. Luego, se vuelve a ejecutar el ciclo de vida del compositor. (Pueden ocurrir tareas de trama y decodificación, pero no se representan aquí).
  2. Se envía un fotograma del compositor a Viz.
  3. Viz agrega los marcos del compositor para el proceso de renderización de foo.com, el proceso de renderización de bar.com y la IU del navegador.
  4. Viz programa un sorteo.
  5. Viz dibuja el marco agregado del compositor en la pantalla.

Para desplazarse por la página web en la pestaña tres:

  1. Una secuencia de eventos input (del mouse, táctil o de teclado) llega al proceso del navegador.
  2. Cada evento se enruta al subproceso compositor del proceso de renderización de baz.com.
  3. El compositor determina si el subproceso principal debe conocer el evento.
  4. El evento se envía, si es necesario, al subproceso principal.
  5. El subproceso principal activa objetos de escucha de eventos input (pointerdown, touchstar, pointermove, touchmove o wheel) para ver si los objetos de escucha llamarán a preventDefault en el evento.
  6. El subproceso principal muestra si se llamó a preventDefault al compositor.
  7. De lo contrario, el evento de entrada se envía de vuelta al proceso del navegador.
  8. El proceso del navegador lo convierte en un gesto de desplazamiento combinándolo con otros eventos recientes.
  9. El gesto de desplazamiento se envía una vez más al subproceso del compositor del proceso de renderización de baz.com.
  10. El desplazamiento se aplica allí y el subproceso del compositor para el proceso de renderización de bar.com marca una animación en el bucle de eventos del compositor. Luego, esto muta el desplazamiento en los árboles de propiedades y vuelve a ejecutar el ciclo de vida del compositor. También le indica al subproceso principal que active un evento scroll (que no se muestra aquí).
  11. Se envía un fotograma del compositor a Viz.
  12. Viz agrega los marcos del compositor para el proceso de renderización de foo.com, el proceso de renderización de bar.com y la IU del navegador.
  13. Viz programa un sorteo.
  14. Viz dibuja el marco agregado del compositor en la pantalla.

Para enrutar un evento click en un hipervínculo del iframe #two en la pestaña uno, haz lo siguiente:

  1. Un evento input (del mouse, táctil o de teclado) llega al proceso del navegador. Realiza una prueba de posicionamiento aproximada para determinar si el proceso de renderización del iframe bar.com debería recibir el clic y lo envía allí.
  2. El subproceso del compositor para bar.com enruta el evento click al subproceso principal de bar.com y programa una tarea de bucle de eventos de renderización para procesarlo.
  3. El procesador de eventos de entrada para las pruebas de posicionamiento del subproceso principal de bar.com determina en qué elemento del DOM se hizo clic en el iframe y activa un evento click para que las secuencias de comandos lo observen. Al escuchar no preventDefault, navega al hipervínculo.
  4. Cuando se carga la página de destino del hipervínculo, se renderiza el estado nuevo, con pasos similares al ejemplo anterior de “DOM de renderización modificada”. (Estos cambios posteriores no se representan aquí).

Conclusión

Esos son muchos detalles. Como puedes ver, la renderización en Chromium es bastante complicada. Puede llevar mucho tiempo recordar e internalizar todas las piezas, así que no te preocupes si parece abrumador.

La conclusión más importante es que existe una canalización de procesamiento simple a nivel conceptual, que se dividió en varios componentes independientes a través de la modularización cuidadosa y la atención al detalle. Luego, estos componentes se dividieron en subprocesos y procesos paralelos para maximizar las oportunidades de rendimiento escalable y extensibilidad.

Cada uno de esos componentes desempeña un papel fundamental para habilitar todo el rendimiento y las funciones que necesitan las aplicaciones web modernas. Pronto publicaremos información detallada sobre cada una de ellas y sus funciones importantes.

Pero antes de eso, también explicaré por qué las estructuras de datos clave que se mencionan en esta publicación (las que se indican en azul a los costados del diagrama de la canalización de renderización) son tan importantes para RenderingNG como los componentes de código.

Gracias por leer esta comunicación, y no te pierdas las novedades.

Ilustraciones de Una Kravets.