Análisis detallado de renderizaciónNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Intermitencia hace referencia a la implementación de Chromium de la plataforma web. Abarca todas las fases de renderización anterior a la composición, que culminan en la confirmación del compositor. Puedes obtener más información sobre la arquitectura de renderización de parpadeo en un artículo anterior de esta serie.

Blink comenzó como una bifurcación de WebKit, que es en sí misma una horquilla de KHTML, que data de 1998. Contiene parte del código más antiguo (y más importante) de Chromium y, para el año 2014, ya estaba mostrando su antigüedad. Ese año, nos embarcamos en un conjunto de proyectos ambiciosos bajo lo que llamamos BlinkNG, con el objetivo de abordar deficiencias prolongadas en la organización y estructura del código de Blink. Este artículo explorará BlinkNG y los proyectos que la conforman: por qué los hicimos, qué lograron, los principios rectores que dieron forma a su diseño y las oportunidades de mejoras futuras que ofrecen.

Canalización de renderización antes y después de BlinkNG.

Renderización previa a la NG

La canalización de renderización dentro de Blink siempre se dividió conceptualmente en fases (style, layout, Paint, etc.), pero las barreras de abstracción tenían fugas. En términos generales, los datos asociados con la renderización constaban de objetos mutables y de larga duración. Estos objetos podían (y se modificaron) en cualquier momento, y con frecuencia se reciclaron y reutilizaron sucesivas actualizaciones de renderización. Era imposible responder de manera confiable preguntas simples como:

  • ¿Es necesario actualizar el resultado de estilo, diseño o pintura?
  • ¿Cuándo obtendrán estos datos su valor?
  • ¿Cuándo es correcto modificar estos datos?
  • ¿Cuándo se borrará este objeto?

Hay muchos ejemplos de esto, como los siguientes:

El estilo generaría ComputedStyle en función de las hojas de estilo. pero ComputedStyle no era inmutable. en algunos casos, se modificaría en etapas posteriores de la canalización.

Style generaría un árbol de LayoutObject y, luego, layout anotaría esos objetos con información de tamaño y posicionamiento. En algunos casos, layout incluso modificaría la estructura del árbol. No había una separación clara entre las entradas y las salidas de layout.

El estilo generaría estructuras de datos complementarias que determinaron el curso de la composición, y esas estructuras de datos se modificaron en cada fase después del estilo.

En un nivel inferior, los tipos de datos de renderización consisten en gran medida de árboles especializados (por ejemplo, el árbol de DOM, el árbol de estilos, el árbol de diseño y el árbol de propiedades de pintura). y la renderización se implementan como recorridos recursivos de árboles. Lo ideal es que el recorrido de un árbol esté contenido: cuando se procese un nodo de árbol determinado, no deberíamos acceder a ninguna información fuera del subárbol con permisos de administrador en ese nodo. Eso nunca fue verdad antes de la renderización anterior. información a la que se accede con frecuencia desde los elementos principales del nodo que se procesa. Esto hizo que el sistema fuera muy frágil y propenso a errores. También era imposible hacer un recorrido a un árbol desde cualquier lugar que no fuera la raíz del árbol.

Por último, había muchas rampas en la canalización de renderización distribuidas en todo el código: diseños forzados activados por JavaScript, actualizaciones parciales activadas durante la carga de documentos, actualizaciones forzadas para prepararse para la orientación de eventos, actualizaciones programadas solicitadas por el sistema de visualización y APIs especializadas expuestas solo para probar código, entre otras. Incluso hubo algunas rutas de acceso recursivas y reentrantes en la canalización de renderización (es decir, saltar al comienzo de una etapa desde la mitad de otra). Cada una de estas plataformas tenía su propio comportamiento idiosincrático y, en algunos casos, el resultado de la renderización dependía de la manera en que se activaba la actualización de renderización.

Qué cambiamos

BlinkNG se compone de muchos subproyectos, grandes y pequeños, todos con el objetivo común de eliminar los déficits arquitectónicos descritos anteriormente. Estos proyectos comparten algunos principios básicos diseñados para hacer que la canalización de renderización sea más similar a una canalización real:

  • Punto de entrada uniforme: Siempre debemos ingresar a la canalización al principio.
  • Etapas funcionales: Cada etapa debe tener entradas y salidas bien definidas, y su comportamiento debe ser funcional, es decir, determinista y repetible, y las salidas deben depender solo de las entradas definidas.
  • Entradas constantes: Las entradas de cualquier etapa deben ser efectivamente constantes mientras se ejecuta la etapa.
  • Resultados inmutables: Una vez que finaliza una etapa, sus resultados deben ser inmutables durante el resto de la actualización de renderización.
  • Coherencia del punto de control: Al final de cada etapa, los datos de renderización producidos hasta el momento deben tener un estado de autocoherencia.
  • Anulación de duplicación del trabajo: Procesa cada elemento una sola vez.

Una lista completa de los subproyectos de BlinkNG obtendría una lectura tediosa, pero las siguientes son algunas consecuencias en particular.

El ciclo de vida del documento

La clase DocumentLifecycle realiza un seguimiento de nuestro progreso a través de la canalización de renderización. Nos permite realizar comprobaciones básicas que aplican las variantes mencionadas anteriormente, como las siguientes:

  • Si modificamos una propiedad ComputedStyle, el ciclo de vida del documento debe ser kInStyleRecalc.
  • Si el estado de DocumentLifecycle es kStyleClean o posterior, NeedsStyleRecalc() debe mostrar false para cualquier nodo conectado.
  • Cuando ingresas a la fase de ciclo de vida de Paint, el estado del ciclo de vida debe ser kPrePaintClean.

En el transcurso de la implementación de BlinkNG, eliminamos sistemáticamente las instrucciones de código que infringían estos invariantes y esparcimos muchas más aserciones en todo el código para asegurarnos de no hacer regresiones.

Si alguna vez revisaste el código de renderización de bajo nivel, quizás te preguntes: “¿Cómo llegué hasta aquí?”. Como se mencionó antes, existe una variedad de puntos de entrada a la canalización de renderización. Anteriormente, esto incluía rutas de llamadas recursivas y reentrantes, y lugares en los que ingresamos a la canalización en una fase intermedia, en lugar de comenzar desde el principio. En el transcurso de BlinkNG, analizamos estas rutas de llamadas y determinamos que todas se redujeron a dos situaciones básicas:

  • Todos los datos de renderización deben actualizarse, por ejemplo, cuando se generan nuevos píxeles para la publicidad gráfica o cuando se realiza una prueba de posicionamiento para la segmentación de eventos.
  • Necesitamos un valor actualizado para una consulta específica que se pueda responder sin actualizar todos los datos de renderización. Esto incluye la mayoría de las consultas de JavaScript, por ejemplo, node.offsetTop.

Ahora solo hay dos puntos de entrada a la canalización de renderización, que corresponden a estas dos situaciones. Las rutas de código de reentrantes se quitaron o refactorizaron, y ya no es posible ingresar a la canalización a partir de una fase intermedia. Esto eliminó mucho misterio sobre cuándo y cómo ocurren las actualizaciones de renderización, lo que facilita mucho el razonamiento sobre el comportamiento del sistema.

Estilo de la canalización, diseño y pintura previa

En conjunto, las fases de renderización antes de Paint son responsables de lo siguiente:

  • Ejecución del algoritmo de cascada de estilo para calcular las propiedades de estilo finales de los nodos del DOM
  • Generación del árbol de diseño que representa la jerarquía de cuadros del documento.
  • Determinación de la información de tamaño y posición para todos los cuadros.
  • Redondear o ajustar la geometría de subpíxeles a límites enteros de píxeles para pintar
  • Determinar las propiedades de las capas compuestas (transformación afín, filtros, opacidad o cualquier otra cosa que se pueda acelerar por GPU)
  • Determinar qué contenido cambió desde la fase de pintura anterior y que debe pintarse o volverse a pintar (invalidación de pintura)

Esta lista no ha cambiado, pero antes de BlinkNG gran parte de este trabajo se realizaba de manera ad hoc, repartida en varias fases de renderización, con muchas funcionalidades duplicadas e ineficiencias integradas. Por ejemplo, la fase de style siempre fue la principal responsable de calcular las propiedades de estilo finales de los nodos. Sin embargo, hubo algunos casos especiales en los que no determinamos los valores finales de las propiedades de diseño hasta que se completó la fase de style. No había un punto formal ni exigible en el proceso de renderización en el que pudiéramos decir con certeza que la información de estilo era inmutable y completa.

Otro buen ejemplo de problema previo a BlinkNG es la invalidación de pintura. Anteriormente, la invalidación de pintura se extendía a lo largo de todas las fases de renderización que conducían a la pintura. Cuando se modificaba el estilo o el código de diseño, era difícil saber qué cambios se necesitaban en la lógica de invalidación de pintura, y era fácil cometer un error que generaba errores de invalidación insuficiente o excesiva. Puedes leer más sobre las complejidades del antiguo sistema de invalidación de pintura en el artículo de esta serie dedicada a LayoutNG.

El ajuste de la geometría de diseño de subpíxeles a los límites de píxeles completos para la pintura es un ejemplo de lo que tuvimos varias implementaciones de la misma funcionalidad y mucho trabajo redundante. El sistema de pintura utilizaba una ruta de código de ajuste de píxeles y una ruta de código completamente independiente que se utilizaba cada vez que necesitábamos un cálculo único y en el momento de las coordenadas ajustadas por píxeles fuera del código de pintura. No hace falta decir que cada implementación tuvo sus propios errores y los resultados no siempre coincidieron. Debido a que no había almacenamiento en caché de esta información, a veces el sistema realizaba exactamente el mismo procesamiento varias veces, otra carga en el rendimiento.

Estos son algunos proyectos importantes que eliminaron las deficiencias arquitectónicas de las fases de renderización antes de la pintura.

Project Squad: Canalización de la fase de diseño

Este proyecto abordó dos déficits principales en la fase de estilo que impidieron que se canalizara sin problemas:

Hay dos resultados principales de la fase de estilo: ComputedStyle, que contiene el resultado de ejecutar el algoritmo de cascada de CSS en el árbol del DOM. y un árbol de LayoutObjects, que establece el orden de las operaciones para la fase de diseño. Conceptualmente, la ejecución del algoritmo en cascada debería ocurrir estrictamente antes de generar el árbol de diseño. pero antes, estas dos operaciones estaban intercaladas. Project Squad tuvo éxito al dividir estos dos elementos en fases secuenciales distintas.

Anteriormente, ComputedStyle no siempre obtenía su valor final durante el ajuste del estilo. En algunas situaciones, se actualizó ComputedStyle durante una fase posterior de la canalización. Project Squad refactorizó correctamente estas instrucciones de código para que ComputedStyle nunca se modifique después de la fase de diseño.

LayoutNG: Canalización de la fase de diseño

Este proyecto monumental, uno de los pilares de RenderingNG, fue una reescritura completa de la fase de renderización del diseño. No haremos justicia a todo el proyecto aquí, pero hay algunos aspectos notables para el proyecto general BlinkNG:

  • Anteriormente, en la fase de diseño, se recibía un árbol de LayoutObject creado por la fase de diseño y se anotó el árbol con información sobre el tamaño y la posición. Por lo tanto, no hubo una separación clara de las entradas y las salidas. LayoutNG introdujo el árbol de fragmentos, que es el resultado principal de solo lectura del diseño y sirve como entrada principal para las fases de renderización posteriores.
  • LayoutNG llevó la propiedad de contención al diseño: cuando calculamos el tamaño y la posición de un LayoutObject determinado, ya no miramos fuera del subárbol con permisos de administrador en ese objeto. Toda la información necesaria para actualizar el diseño de un objeto determinado se calcula de antemano y se proporciona como una entrada de solo lectura al algoritmo.
  • Anteriormente, había casos extremos en los que el algoritmo de diseño no era estrictamente funcional: el resultado del algoritmo dependía de la actualización de diseño anterior más reciente. LayoutNG eliminó esos casos.

La fase de pintura previa

Anteriormente, no había una fase formal de renderización de la imagen previa, solo una bolsa de operaciones posteriores al diseño. La fase de prepintura surgió del reconocimiento de que había algunas funciones relacionadas que podrían implementarse mejor como un recorrido sistemático del árbol de diseño una vez que se complete el diseño. y lo más importante:

  • Emitir invalidaciones de pintura: Es muy difícil realizar una invalidación de pintura de forma correcta durante el curso del diseño, cuando la información está incompleta. Es mucho más fácil hacerlo bien, y puede ser muy eficiente si se divide en dos procesos distintos: durante el estilo y el diseño, el contenido se puede marcar con una función experimental booleana simple como "posiblemente se necesita invalidación de pintura". Durante el recorrido del árbol previo a la pintura, verificamos estas marcas y emitimos invalidaciones según sea necesario.
  • Generación de árboles de propiedades de pintura: Es un proceso que se describe con más detalle.
  • Procesamiento y registro de ubicaciones de pintura ajustada de píxeles: Los resultados registrados se pueden usar en la fase de pintura y en cualquier código downstream que los necesite, sin ningún cálculo redundante.

Árboles de propiedades: geometría coherente

Los árboles de propiedades se introdujeron en una etapa temprana de RenderingNG para abordar la complejidad del desplazamiento, que en la Web tiene una estructura diferente a la de todos los demás tipos de efectos visuales. Antes de los árboles de propiedades, el compositor de Chromium usaba una sola "capa" para representar la relación geométrica del contenido compuesto, pero que se descompuso rápidamente cuando se hicieron evidentes las complejidades completas de los componentes, como position:fixed. La jerarquía de capas aumentó los punteros no locales adicionales, lo que indica el elemento superior de desplazamiento. o “superior del clip” de una capa, y en poco tiempo era muy difícil entender el código.

Los árboles de propiedades solucionaron este problema representando los aspectos de desplazamiento de desbordamiento y recorte del contenido independientemente de todos los demás efectos visuales. Esto permitió modelar correctamente la verdadera estructura visual y de desplazamiento de los sitios web. A continuación, "todos" que tuvimos que hacer fue implementar algoritmos sobre los árboles de propiedades, como la transformación de espacio-pantalla de capas compuestas o determinar qué capas se desplazaban y cuáles no.

De hecho, pronto notamos que había muchos otros lugares en el código donde se planteaban preguntas geométricas similares. (La publicación sobre estructuras de datos clave contiene una lista más completa). Varias de ellas tenían implementaciones duplicadas de lo mismo que hacía el código compositor; todas tenían un subconjunto diferente de errores; y ninguna de ellas modeló adecuadamente la verdadera estructura del sitio web. La solución quedó clara: centraliza todos los algoritmos de geometría en un solo lugar y refactoriza todo el código para usarlo.

A su vez, todos estos algoritmos dependen de los árboles de propiedades, por lo que los árboles de propiedades son una estructura de datos clave (es decir, que se usa en toda la canalización) de RenderingNG. Por lo tanto, para lograr este objetivo de un código de geometría centralizado, necesitábamos introducir el concepto de árboles de propiedades mucho antes en la canalización, en la pintura previa, y cambiar todas las APIs que ahora dependían de ellas para requerir que se ejecutara la pintura previa antes de que pudieran ejecutarse.

Esta historia es otro aspecto del patrón de refactorización BlinkNG: identifica procesamientos clave, refactoriza para evitar duplicarlos y crea etapas de canalización bien definidas que crean las estructuras de datos que los alimentan. Calculamos los árboles de propiedades exactamente en el momento en que toda la información necesaria está disponible. y nos aseguramos de que los árboles de propiedades no puedan cambiar mientras se ejecutan las etapas de renderización posteriores.

Composición después de la pintura: Pintura para tuberías y composición

La capacitación es el proceso de determinar qué contenido del DOM entra en su propia capa compuesta (que, a su vez, representa una textura de GPU). Antes de RenderingNG, la creación de capas se ejecutó antes de la pintura, no después (consulte aquí la canalización actual; observa el cambio de orden). Primero, decidiríamos qué partes del DOM se incluyeron en qué capa compuesta y, luego, dibujaríamos listas de visualización para esas texturas. Naturalmente, las decisiones dependían de factores como qué elementos del DOM se animaban o desplazaban, o tenían transformaciones 3D, y qué elementos se pintaban sobre cuáles.

Esto causó grandes problemas, ya que era más o menos necesario que haya dependencias circulares en el código, lo que es un gran problema para una canalización de renderización. Veamos por qué con un ejemplo. Supongamos que debemos invalidar la pintura (es decir, que debemos volver a dibujar la lista de visualización y volver a generar la trama). La necesidad de invalidar puede provenir de un cambio en el DOM, o de un estilo o diseño modificado. Pero, por supuesto, solo nos gustaría invalidar las partes que en realidad cambiaron. Eso significó descubrir qué capas compuestas se vieron afectadas y, luego, invalidar parte o todas las listas de visualización de esas capas.

Esto significa que la invalidación dependía del DOM, el estilo, el diseño y las decisiones de creación de capas pasadas (pasado: el significado del fotograma renderizado anterior). Sin embargo, la creación de capas actual también depende de todo esto. Y como no teníamos dos copias de todos los datos de estratificación, era difícil diferenciar entre las decisiones de estratificación pasada y futuras. Así que obtuvimos mucho código con un razonamiento circular. A veces, esto conducía a códigos ilógicos o incorrectos, o incluso a fallas o problemas de seguridad, si no fuéramos muy cuidadosos.

Para lidiar con esta situación, antes presentamos el concepto del objeto DisableCompositingQueryAsserts. La mayoría de las veces, si el código intentaba consultar decisiones de creación de capas pasadas, provocaba una falla de aserción y fallaba el navegador si estuviera en modo de depuración. Esto nos ayudó a evitar la introducción de nuevos errores. Además, en cada caso en que el código realmente necesitaba consultar decisiones de creación de capas anteriores, colocamos el código para permitirlo asignando un objeto DisableCompositingQueryAsserts.

Con el tiempo, nuestro plan consistía en deshacernos de todos los objetos DisableCompositingQueryAssert de los sitios de llamadas y, luego, declarar que el código era seguro y correcto. Pero lo que descubrimos es que algunas de las llamadas eran esencialmente imposibles de quitar siempre que la creación de capas se realizara antes de la pintura. (Por fin pudimos quitarlo hace poco). Esta fue la primera razón descubierta para el proyecto compuesto After Paint. Lo que aprendimos fue que, incluso si tienes una fase de canalización bien definida para una operación, si se encuentra en el lugar incorrecto de la canalización, terminarás por detenerte.

La segunda razón para el proyecto compuesto After Paint fue el error de composición básica. Una forma de informar este error es que los elementos DOM no son una buena representación 1:1 de un esquema de capas eficiente o completo para el contenido de las páginas web. Y como la composición era antes de la pintura, dependía intrínsecamente de los elementos del DOM y no mostraba listas ni árboles de propiedades. Esto es muy similar al motivo por el que presentamos los árboles de propiedades y, al igual que con los árboles de propiedades, la solución se descarta directamente si determinas la fase correcta de la canalización, la ejecutas en el momento correcto y le proporcionas las estructuras de datos clave correctas. Al igual que con los árboles de propiedades, esta fue una buena oportunidad para garantizar que, una vez que se completa la fase de pintura, su resultado es inmutable para todas las fases posteriores de la canalización.

Beneficios

Como pudiste ver, una canalización de renderización bien definida ofrece enormes beneficios a largo plazo. Hay incluso más de lo que crees:

  • Confiabilidad considerablemente mejorada: Esta es bastante sencilla. Un código más limpio con interfaces bien definidas y comprensibles es más fácil de entender, escribir y probar. Esto lo hace más confiable. También hace que el código sea más seguro y estable, con menos fallas y menos errores de uso después de la liberación.
  • Cobertura ampliada de pruebas: En el transcurso de BlinkNG, agregamos muchas pruebas nuevas a nuestro paquete. Esto incluye pruebas de unidades que proporcionan una verificación enfocada de los componentes internos. las pruebas de regresión que nos impiden volver a introducir errores antiguos que hemos corregido (¡muchos!); y muchas incorporaciones al paquete de pruebas de plataforma web público mantenido de forma colectiva, que todos los navegadores usan para medir el cumplimiento de los estándares web.
  • Más fácil de extender: Si un sistema se divide en componentes claros, no es necesario comprender otros componentes en ningún nivel de detalle para avanzar en el actual. Esto hace que sea más fácil para todos agregar valor al código de renderización sin tener que ser un gran experto, y también facilita razonar sobre el comportamiento de todo el sistema.
  • Rendimiento: Optimizar los algoritmos escritos en código espagueti es bastante difícil, pero es casi imposible lograr cosas incluso más grandes, como el desplazamiento y las animaciones universales o los procesos y subprocesos para el aislamiento de sitios sin una canalización de este tipo. El paralelismo puede ayudarnos a mejorar el rendimiento enormemente, pero también es extremadamente complicado.
  • Rendimiento y contención: BlinkNG implementó varias funciones nuevas que ejercitan la canalización de formas novedosas. Por ejemplo, ¿qué sucede si solo queremos ejecutar la canalización de renderización hasta que venza el presupuesto? ¿O bien puedes omitir la renderización de los subárboles que se sabe que no son relevantes para el usuario en este momento? Eso es lo que habilita la propiedad content-visibility de CSS. ¿Qué sucede con hacer que el estilo de un componente dependa de su diseño? Son las consultas de contenedores.

Caso de éxito: Consultas de contenedores

Las consultas de contenedores son una de las funciones de plataforma web más esperadas y solicitadas por los desarrolladores de CSS durante años. Si es tan genial, ¿por qué aún no existe? Esto se debe a que una implementación de consultas de contenedor requiere una comprensión y un control muy cuidadosos de la relación entre el estilo y el código de diseño. Veamos los detalles.

Una consulta de contenedor permite que los estilos que se aplican a un elemento dependan del tamaño repartido de un elemento principal. Dado que el tamaño repartido se calcula durante el diseño, debemos ejecutar el ajuste del estilo después del diseño. pero el ajuste de estilo se ejecuta antes que el diseño. Esta paradoja del pollo y el huevo es el motivo por el que no podíamos implementar consultas de contenedores antes de BlinkNG.

¿Cómo podemos resolver esto? ¿No es una dependencia de canalización hacia atrás, es decir, el mismo problema que se resuelven los proyectos como Composite After Paint? Y lo que es peor, ¿qué pasa si los nuevos estilos cambian el tamaño del principal? ¿A veces esto no generará un bucle infinito?

En principio, la dependencia circular se puede resolver usando la propiedad "Contiene CSS", que permite que la renderización fuera de un elemento no dependa de la renderización dentro del subárbol de ese elemento. Eso significa que los nuevos estilos que aplica un contenedor no pueden afectar su tamaño, ya que las consultas de contenedor requieren contención.

Pero, en realidad, eso no fue suficiente, y fue necesario agregar un tipo de contención más débil que solo la contención del tamaño. Esto se debe a que es común que el contenedor de consultas de contenedores pueda cambiar el tamaño en una sola dirección (por lo general, bloque) en función de sus dimensiones intercaladas. Por lo tanto, se agregó el concepto de contención de tamaño intercalado. Sin embargo, como puedes ver en la nota muy larga de esa sección, no estuvo nada claro por mucho tiempo si la contención del tamaño de la línea era posible.

Una cosa es describir la contención en lenguaje de especificaciones abstractas y otra es muy diferente implementarla correctamente. Recuerda que uno de los objetivos de BlinkNG era llevar el principio de contención a los recorridos de árboles que constituyen la lógica principal de la representación: cuando se atraviesa un subárbol, no se necesita información del exterior del subárbol. A medida que sucede (bueno, no fue exactamente un accidente), es mucho mucho más prolijo y fácil de implementar la contención de CSS si el código de renderización cumple con el principio de contención.

Futuro: Composición fuera del subproceso principal... ¡y más!

La canalización de procesamiento que se muestra aquí, en realidad, está un poco adelantada a la implementación actual de RenderingNG. Muestra que la asignación de capas está fuera del subproceso principal, mientras que, por el momento, aún está en el subproceso principal. Sin embargo, solo es cuestión de tiempo para que esto se haga, ahora que se envió Composite After Paint y la creación de capas es posterior a la pintura.

Para entender por qué esto es importante y adónde puede llevarlo, debemos considerar la arquitectura del motor de renderización desde un punto de vista un poco más alto. Uno de los obstáculos más duraderos para mejorar el rendimiento de Chromium es el simple hecho de que el subproceso principal del procesador controla tanto la lógica principal de la aplicación (es decir, la ejecución de la secuencia de comandos) como la mayor parte del procesamiento. Como resultado, el subproceso principal suele estar saturado de trabajo y la congestión del subproceso principal suele ser el cuello de botella en todo el navegador.

La buena noticia es que no tiene por qué ser así. Este aspecto de la arquitectura de Chromium se remonta a los días de KHTML, cuando la ejecución de subproceso único era el modelo de programación predominante. En el momento en que los procesadores de varios núcleos se volvieron comunes en los dispositivos para consumidores, la suposición de un solo subproceso se integraba por completo a Blink (antes WebKit). Durante mucho tiempo, quisimos introducir más subprocesos en el motor de renderización, pero era simplemente imposible en el sistema anterior. Uno de los principales objetivos de la renderización de NG fue salir de este agujero y hacer posible mover el trabajo de renderización, de forma parcial o total, a otro subproceso (o subprocesos).

Ahora que BlinkNG está a punto de finalizar, ya estamos empezando a explorar esta área. Non-Blocking Commit es la primera incursión en el cambio del modelo de subprocesos del procesador. La confirmación del compositor (o solo confirmación) es un paso de sincronización entre el subproceso principal y el del compositor. Durante la confirmación, hacemos copias de los datos de renderización que se producen en el subproceso principal para que las use el código compuesto en sentido descendente que se ejecuta en el subproceso del compositor. Mientras se realiza esta sincronización, se detiene la ejecución del subproceso principal mientras se ejecuta el código de copia en el subproceso compositor. Esto se hace para garantizar que el subproceso principal no modifique sus datos de renderización mientras el subproceso compositor los copia.

La confirmación sin bloqueo eliminará la necesidad de que el subproceso principal se detenga y espere a que finalice la etapa de confirmación; el subproceso principal continuará trabajando mientras la confirmación se ejecuta de forma simultánea en el subproceso compositor. El efecto neto de la confirmación sin bloqueo será una reducción del tiempo dedicado a renderizar el trabajo en el subproceso principal, lo que disminuirá la congestión en ese subproceso y mejorará el rendimiento. En el momento en que se redacta este documento (marzo de 2022), tenemos un prototipo en funcionamiento de un compromiso no bloqueador y nos estamos preparando para hacer un análisis detallado de su impacto en el rendimiento.

En las alitas, se muestra la composición del subproceso principal fuera del subproceso principal, con el objetivo de hacer que el motor de renderización coincida con la ilustración moviendo la capacitación fuera del subproceso principal y hacia un subproceso de trabajo. Al igual que el compromiso sin bloqueo, esto reducirá la congestión en el subproceso principal, ya que disminuirá la carga de trabajo de renderización. Un proyecto como este nunca hubiera sido posible sin las mejoras arquitectónicas de Composite After Paint.

Y hay más proyectos en la canalización (juego de palabras). Finalmente tenemos una base que permite experimentar con el trabajo de renderización redistribuible, y nos entusiasma ver lo que es posible.