Aquí encontrarás cómo se configuran los componentes de RenderingNG y cómo fluye la canalización de renderización a través de ellos.
A partir del nivel más alto, las tareas de renderización son las siguientes:
- Renderiza el contenido en píxeles en la pantalla.
- Anima los efectos visuales en el contenido de un estado a otro.
- Desplázate en respuesta a la entrada.
- Enruta la entrada de manera eficiente a los lugares correctos para que las secuencias de comandos de los desarrolladores 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 interfaz del navegador. Además, una transmisión de eventos de entrada sin procesar de pantallas táctiles, ratones, 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 descendientes de iframe recursivos.
Un efecto visual es una operación gráfica que se aplica a un mapa de bits, como desplazamiento, transformación, recorte, filtro, opacidad o combinación.
Componentes de la arquitectura
En RenderingNG, estas tareas se dividen de forma lógica en varias etapas y componentes de código. Los componentes terminan en varios procesos, subprocesos y subcomponentes de la CPU dentro de esos subprocesos. Cada uno desempeña un papel importante en la obtención de confiabilidad, rendimiento escalable y extensibilidad para todo el contenido web.
Estructura de la canalización de renderización
La renderización se realiza en una canalización con varias etapas y artefactos que se crean a lo largo del proceso. Cada etapa representa un 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.
Las etapas son las siguientes:
- Animar: Cambia los diseños calculados y muta los árboles de propiedades con el tiempo en función de cronogramas declarativos.
- Estilo: Aplica CSS al DOM y crea estilos calculados.
- 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.
- Pre-paint: Calcula los árboles de propiedades y invalidate las listas de visualización y las baldosas de textura de la GPU existentes según corresponda.
- Desplazamiento: Actualiza el desplazamiento de documentos y elementos del DOM desplazables mediante la mutación de árboles de propiedades.
- Paint: Calcula una lista de visualización que describa cómo rasterizar mosaicos de textura de la GPU desde el DOM.
- Confirmación: Copia los árboles de propiedades y la lista de visualización en el subproceso del compositor.
- Dividir en capas: Divide la lista de visualización en una lista de capas compuestas para la rasterización y animación independientes.
- Trabajos en mosaico de rasterización, decodificación y pintura: Convierte las listas de visualización, las imágenes codificadas y el código de trabajo en mosaico de pintura, respectivamente, en baldosas de textura de la GPU.
- Activar: Crea un marco de compositor que represente cómo dibujar y posicionar las tarjetas de la GPU en la pantalla, junto con los efectos visuales.
- Agrupa: Combina los fotogramas del compositor de todos los fotogramas visibles en un solo fotograma del compositor global.
- Dibujo: Ejecuta el fotograma del compositor agregado en la GPU para crear píxeles en pantalla.
Se pueden omitir las etapas de la canalización de renderización si no son necesarias. Por ejemplo, las animaciones de efectos visuales y el desplazamiento pueden omitir el diseño, la pintura previa y la pintura. Por este motivo, la animación y el desplazamiento se marcan 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, 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 muestra 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). El video (que tampoco se representa directamente) generalmente se renderiza con un código independiente que decodifica fotogramas en mosaicos de textura de la GPU que, luego, se conectan a los fotogramas del compositor y al paso de dibujo.
Estructura de procesos y subprocesos
Procesos de la CPU
El uso de varios procesos de CPU logra el aislamiento de rendimiento y seguridad entre los sitios y el estado del navegador, y la estabilidad y el aislamiento de seguridad del hardware de la GPU.
- El proceso de renderización renderiza, anima, desplaza y enruta la entrada para una combinación de pestaña y sitio únicos. Existen varios procesos de renderización.
- El proceso del navegador renderiza, anima y enruta la entrada para la IU del navegador (incluida la barra de direcciones, los títulos de las pestañas y los íconos) y enruta todas las entradas restantes al proceso de renderización adecuado. Hay un proceso de navegador.
- El proceso de visualización agrega la composición de varios procesos de renderización, además del proceso del navegador. Realiza la rasterización y el dibujo con la GPU. Hay un proceso de Viz.
Los diferentes sitios siempre terminan en diferentes procesos de renderización.
Por lo general, varias pestañas o ventanas del navegador del mismo sitio se incluyen en diferentes procesos de renderización, a menos que las pestañas estén relacionadas, por ejemplo, si una abre la otra. Cuando hay una gran presión de memoria en Chromium para computadoras, es posible que se coloquen varias pestañas del mismo sitio en el mismo proceso de renderización, incluso si no están relacionadas.
Dentro de una sola pestaña del navegador, los fotogramas de diferentes sitios siempre están en diferentes procesos de renderización, pero los fotogramas del mismo sitio siempre están en el mismo proceso de renderización. Desde la perspectiva de la renderización, la ventaja importante de los múltiples procesos de renderización es que los iframes y las pestañas entre sitios logran el aislamiento de rendimiento entre sí. Además, los orígenes pueden habilitar aún más aislamiento.
Hay exactamente un proceso de Viz para todo Chromium, ya que, por lo general, solo hay una GPU y una pantalla para dibujar.
Separar Viz en su propio proceso es bueno para la estabilidad frente a errores en los controladores o el hardware de la GPU. También es bueno para el aislamiento de seguridad, que es importante para las APIs de GPU, como Vulkan y la seguridad en general.
Dado que el navegador puede tener muchas pestañas y ventanas, y todos 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 se enfoca uno a la vez. De hecho, las pestañas del navegador no visibles están mayormente desactivadas y pierden toda su memoria de 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 (conocidas como WebUI). Esto no se hace por motivos de aislamiento de 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 el 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 hay un proceso de utilidad para decodificar contenido de video protegido. Este proceso no se muestra en los diagramas anteriores.
Subprocesos
Los subprocesos ayudan a lograr el aislamiento de rendimiento y la capacidad de respuesta a pesar de las tareas lentas, la paralelización de canalizaciones y el búfer múltiple.
- El subproceso principal ejecuta secuencias de comandos, el bucle de eventos de renderización, el ciclo de vida del documento, las pruebas de hit, el envío de eventos de secuencia de comandos y el análisis de HTML, CSS y otros formatos de datos.
- Los ayudantes de subprocesos principales realizan tareas como crear imágenes de mapas de bits y BLOB que requieren codificación o decodificación.
- Web Workers ejecutan la secuencia de comandos y un bucle de eventos de renderización para OffscreenCanvas.
- El subproceso de compositor procesa eventos de entrada, realiza animaciones y desplazamiento del contenido web, calcula la estratificación óptima del contenido web y coordina las decodificaciones de imágenes, las tareas de rasterización y las tareas de pintura.
- Los ayudantes de subprocesos del compositor coordinan las tareas de trama de Viz y ejecutan tareas de decodificación de imágenes, pintan worklets y raster de resguardo.
- Los subprocesos de salida de audio, demuxer o multimedia 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 principal y del compositor es de vital importancia 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 pueden terminar en el mismo proceso. Sin embargo, existe un aislamiento de rendimiento del trabajo realizado en varias APIs de navegadores. Por ejemplo, la generación de mapas de bits y objetos blob de imagen en la API de Canvas se ejecuta en un subproceso auxiliar del subproceso principal.
Del mismo modo, solo hay un subproceso de compositor por proceso de renderización. Por lo general, no es un problema que haya uno solo, 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. Los subprocesos de trabajo del compositor coordinan las tareas que se ejecutan en el proceso de Viz, pero la aceleración de la GPU en todas partes puede fallar por razones fuera del 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 están menos limitados por la batería que los dispositivos móviles. Este es un ejemplo de escalamiento hacia arriba y hacia abajo.
La arquitectura de subprocesos del proceso de renderización es una aplicación de tres patrones de optimización diferentes:
- Subprocesos auxiliares: Envía subtareas de larga duración a subprocesos adicionales para mantener el subproceso superior responsivo a otras solicitudes simultáneas. Los subprocesos auxiliares del subproceso principal y del compositor son buenos ejemplos de esta técnica.
- Búfer múltiple: Muestra contenido renderizado anteriormente mientras se renderiza contenido nuevo para ocultar la latencia de la renderización. El subproceso del compositor usa esta técnica.
- Paralelización de canalización: Ejecuta la canalización de renderización en varios lugares de forma simultánea. De esta manera, el desplazamiento y la animación pueden ser rápidos; incluso si se produce una actualización de renderización del subproceso principal, el desplazamiento y la animación pueden ejecutarse en paralelo.
Proceso del navegador
- 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 organiza y pinta la IU del navegador.
- Los ayudantes de subprocesos de renderización y composición ejecutan tareas de decodificación de imágenes y rasterización o decodificación de resguardo.
El subproceso de renderización y composición del proceso del navegador son similares al código y la funcionalidad de un proceso de renderización, excepto que el subproceso principal y el compositor se combinan en uno. En este caso, solo se necesita un subproceso porque no se necesita el aislamiento de rendimiento de las tareas de subproceso principal largas, ya que no hay ninguna de forma predeterminada.
Proceso de visualización
- El subproceso principal de la GPU rasteriza listas de visualización y fotogramas de video en tarjetas de textura de la GPU y dibuja fotogramas del compositor en la pantalla.
- El subproceso del compositor de pantalla agrega y optimiza la composición de cada proceso de renderización, además del proceso del navegador, en un solo fotograma del compositor para la presentación en la pantalla.
Por lo general, el procesamiento en ráster y el dibujo se realizan en el mismo subproceso, ya que ambos dependen de los recursos de la GPU, y es difícil usar la GPU de forma confiable en varios subprocesos (el acceso en varios subprocesos a la GPU más fácil es una motivación para desarrollar el nuevo estándar Vulkan). En Android WebView, hay un subproceso de renderización independiente a nivel del SO para el dibujo debido a la forma en que los WebViews se incorporan en una app nativa. Es probable que otras plataformas tengan un subproceso de este tipo en el futuro.
El compositor de pantalla se encuentra en un subproceso diferente porque debe ser responsivo en todo momento y no bloquearse en ninguna fuente posible de ralentización en el subproceso principal de la GPU. Una causa de la ralentización del subproceso principal de la GPU son las llamadas a código que no es de Chrome, como los controladores de GPU específicos del proveedor, que pueden ser lentos de formas difíciles de predecir.
Estructura del componente
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.
Renderiza los componentes del subproceso principal del proceso
En el renderizador Blink, haz lo siguiente:
- El fragmento de árbol de marcos locales representa el árbol de marcos locales y el DOM dentro de los marcos.
- El componente APIs de DOM y Canvas contiene implementaciones de todas estas APIs.
- El ejecutor del ciclo de vida de los documentos ejecuta los pasos de la canalización de renderización hasta el paso de confirmación inclusive.
- El componente de envío y prueba de hit de eventos de entrada ejecuta pruebas de hit para averiguar a qué elemento DOM está dirigido un evento y ejecuta los algoritmos de envío de eventos de entrada y los comportamientos predeterminados.
El programador y ejecutor del bucle 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.
Los fragmentos del árbol de tramas locales son un poco complicados. Recuerda que un árbol de marcos es la página principal y sus iframes secundarios, de forma recursiva. Un fotograma es local para un proceso de renderización si se renderiza en ese proceso, y, de lo contrario, es remoto.
Puedes imaginarte cómo se colorean los fotogramas según su proceso de renderización. En la imagen anterior, los círculos verdes son todos los fotogramas de un proceso de renderización, los naranjas están en un segundo y el azul está en un tercero.
Un fragmento de árbol de trama local es un componente conectado del mismo color en un árbol de trama. Hay cuatro árboles de tramas locales en la imagen: dos para el sitio A, uno para el sitio B y uno para el sitio C. Cada árbol de marcos local obtiene su propio componente del renderizador Blink. El renderizador Blink de un árbol de marcos locales puede estar o no en el mismo proceso de renderización que otros árboles de marcos locales. Se determina según la forma en que se seleccionan los procesos de renderización, como se describió anteriormente.
Estructura del subproceso del compositor del proceso de renderización
Entre los componentes del compositor del proceso de renderización, se incluyen los siguientes:
- 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, rasterización, y 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 pruebas de entrada y de hit realiza el procesamiento de entrada y las pruebas de hit en la resolución de capas compuestas para determinar si los gestos de desplazamiento se pueden ejecutar en el subproceso del compositor y a qué pruebas de hit del proceso de renderización se deben orientar.
Arquitectura de ejemplo en la práctica
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 el componente de estas pestañas se ve de la siguiente manera:
Veamos un ejemplo de cada una de las cuatro tareas principales de renderización. Recordatorio:
- Renderiza el contenido en píxeles en la pantalla.
- Anima los efectos visuales en el contenido de un estado a otro.
- Desplázate en respuesta a la entrada.
- Enruta la entrada de manera eficiente a los lugares correctos para que las secuencias de comandos de los desarrolladores y otros subsistemas puedan responder.
Para renderizar el DOM modificado para la pestaña uno, haz lo siguiente:
- Una secuencia de comandos de desarrollador cambia el DOM en el proceso de renderización de foo.com.
- El renderizador de Blink le indica al compositor que necesita que se realice una renderización.
- El compositor le indica a Viz que debe realizarse una renderización.
- Viz indica el inicio de la renderización al compositor.
- El compositor reenvía la señal de inicio al renderizador de Blink.
- El ejecutor del bucle de eventos del subproceso principal ejecuta el ciclo de vida del documento.
- El subproceso principal envía el resultado al subproceso del compositor.
- El ejecutor de bucle de eventos del compositor ejecuta el ciclo de vida de composición.
- Todas las tareas de ráster se envían a Viz para ráster (a menudo, hay más de una de estas tareas).
- Viz rasteriza el contenido en la GPU.
- Viz confirma que se completó la tarea de rasterización. Nota: A menudo, Chromium no espera a que se complete el raster y, en su lugar, usa un elemento llamado token de sincronización que las tareas de rasterización deben resolver antes de que se ejecute el paso 15.
- Se envía un fotograma del compositor a Viz.
- Viz agrega los fotogramas del compositor para el proceso de renderización de foo.com, el proceso de renderización de iframe de bar.com y la IU del navegador.
- Viz programa un sorteo.
- Viz dibuja el fotograma del compositor agregado en la pantalla.
Para animate una transición de transformación de CSS en la pestaña dos, haz lo siguiente:
- El subproceso del compositor para el proceso de renderización de bar.com marca una animación en su bucle de eventos de compositor mediante la mutación de los árboles de propiedades existentes. Luego, se vuelve a ejecutar el ciclo de vida del compositor. (Es posible que se produzcan tareas de rasterización y decodificación, pero no se muestran aquí).
- Se envía un fotograma del compositor a Viz.
- Viz agrega los fotogramas del compositor para el proceso de renderización de foo.com, el proceso de renderización de bar.com y la IU del navegador.
- Viz programa un sorteo.
- Viz dibuja el fotograma del compositor agregado en la pantalla.
Para desplazarte por la página web en la pestaña tres, haz lo siguiente:
- Una secuencia de eventos
input
(mouse, táctil o teclado) llega al proceso del navegador. - Cada evento se enruta al subproceso del compositor del proceso de renderización de baz.com.
- El compositor determina si el subproceso principal necesita saber sobre el evento.
- Si es necesario, el evento se envía al subproceso principal.
- El subproceso principal activa objetos de escucha de eventos
input
(pointerdown
,touchstar
,pointermove
,touchmove
owheel
) para ver si los objetos de escucha llamarán apreventDefault
en el evento. - El subproceso principal muestra si se llamó a
preventDefault
al compositor. - De lo contrario, el evento de entrada se vuelve a enviar al proceso del navegador.
- El proceso del navegador lo convierte en un gesto de desplazamiento combinándolo con otros eventos recientes.
- El gesto de desplazamiento se vuelve a enviar al subproceso del compositor del proceso de renderización de baz.com.
- Allí se aplica el desplazamiento, y el subproceso del compositor para el proceso de renderización de bar.com marca una animación en su bucle de eventos del compositor.
Luego, se muta el desplazamiento en los árboles de propiedades y se 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í). - Se envía un fotograma del compositor a Viz.
- Viz agrega los fotogramas del compositor para el proceso de renderización de foo.com, el proceso de renderización de bar.com y la IU del navegador.
- Viz programa un sorteo.
- Viz dibuja el fotograma del compositor agregado en la pantalla.
Para enrutar un evento click
en un hipervínculo en el iframe número dos de la pestaña uno, haz lo siguiente:
- El proceso del navegador recibe un evento
input
(mouse, táctil o teclado). Realiza una prueba de hit aproximada para determinar que el proceso de renderización del iframe de bar.com debe recibir el clic y lo envía allí. - El subproceso de 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. - El procesador de eventos de entrada para las pruebas de hit del subproceso principal de bar.com determina en qué elemento DOM del iframe se hizo clic y activa un evento
click
para que las secuencias de comandos lo observen. Si no escuchapreventDefault
, navega al hipervínculo. - Cuando se carga la página de destino del hipervínculo, se renderiza el estado nuevo, con pasos similares al ejemplo anterior "renderiza el DOM modificado". (Estos cambios posteriores no se muestran aquí).
Conclusión
Puede llevar mucho tiempo recordar y, luego, interiorizar cómo funciona la renderización.
La conclusión más importante es que la canalización de renderización, a través de una modularización cuidadosa y una atención a los detalles, se dividió en varios componentes independientes. Luego, estos componentes se dividieron en procesos y subprocesos paralelos para maximizar las oportunidades de rendimiento escalable y extensibilidad.
Cada componente desempeña un papel fundamental en la habilitación del rendimiento y las funciones de las apps web modernas.
Sigue leyendo sobre las estructuras de datos clave, que son tan importantes para RenderingNG como los componentes de código.
Ilustraciones de Una Kravets.