Análisis detallado de renderizaciónNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink hace referencia a la implementación de Chromium de la plataforma web y abarca todas las fases del procesamiento previo a la composición, que culmina en la confirmación del compositor. Puedes obtener más información sobre la arquitectura de renderización intermitente en un artículo anterior de esta serie.

Blink comenzó como una bifurcación de WebKit, que a su vez es una bifurcación de KHTML, que data de 1998. Contiene algunos de los códigos más antiguos (y más importantes) de Chromium y, para 2014, definitivamente ya estaba mostrando su antigüedad. Ese año, emprendimos un conjunto de proyectos ambiciosos bajo el banner de lo que llamamos BlinkNG, con el objetivo de abordar deficiencias de larga data en la organización y estructura del código Blink. En este artículo, exploraremos BlinkNG y los proyectos que la componen: por qué los hicimos, qué lograron, los principios rectores que moldearon su diseño y las oportunidades de mejoras futuras que pueden ofrecer.

La canalización de renderización antes y después de BlinkNG

Renderización previa a NG

La canalización de renderización en Blink siempre se dividía conceptualmente en fases (estilo, diseño, pintura, etc.), pero las barreras de abstracción se estaban filtrando. En términos generales, los datos asociados con la renderización constaban de objetos mutables de larga duración. Estos objetos podían (y se los) modificaron en cualquier momento y, con frecuencia, se reciclaron y reutilizaron mediante actualizaciones de renderización sucesivas. 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 “final”?
  • ¿Cuándo es correcto modificar estos datos?
  • ¿Cuándo se borrará este objeto?

Hay muchos ejemplos de esto, entre ellos:

Style generaría objetos ComputedStyle basados en 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 de árbol. No había una separación clara entre las entradas y salidas del diseño.

El estilo generaría estructuras de datos de accesorios que determinaban el curso de la composición, y esas estructuras se modificaron en el lugar correspondiente en cada fase después del diseño.

En un nivel inferior, la renderización de tipos de datos consta en gran medida de árboles especializados (por ejemplo, el árbol del DOM, el árbol de estilo, el árbol de diseño y el árbol de propiedades de pintura); y las fases de renderización se implementan como recorridos de árbol recursivos. Idealmente, un recorrido del árbol debería estar contenido: cuando se procesa un nodo de árbol determinado, no deberíamos acceder a ninguna información fuera del subárbol que se base en ese nodo. Eso nunca fue cierto antes de la renderizaciónNG; el árbol recorre la información a la que se accede con frecuencia de los principales del nodo que se procesa. Esto hacía que el sistema fuera muy frágil y propenso a errores. Además, era imposible comenzar a caminar desde cualquier lugar que no fuera desde la raíz del árbol.

Por último, hubo muchas rampas de acceso en la canalización de renderización a lo largo del código: diseños forzados activados por JavaScript, actualizaciones parciales que se activaron 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 a código de prueba, por nombrar algunas. Incluso hubo algunas rutas recursivas y reentrantes en la canalización de renderización (es decir, saltaron al comienzo de una etapa desde el medio de otra). Cada una de estas plataformas de acceso tenía su propio comportamiento idiosincrático y, en algunos casos, el resultado de la renderización dependía de la forma en que se activaba la actualización de la renderización.

Qué cambiamos

BlinkNG se compone de muchos subproyectos, grandes y pequeños, todos con el objetivo compartido de eliminar los déficits arquitectónicos descritos anteriormente. Estos proyectos comparten algunos principios orientativos diseñados para hacer que la canalización de renderización sea 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.
  • Salidas inmutables: una vez que finaliza una etapa, sus resultados deben ser inmutables durante el resto de la actualización de renderización.
  • Coherencia en los puntos de control: Al final de cada etapa, los datos de renderización producidos hasta ahora deben tener un estado autocoherente.
  • Anulación de duplicación del trabajo: solo procesa cada elemento una vez.

Una lista completa de los subproyectos de BlinkNG implicaría una lectura tediosa, pero a continuación se mencionan algunas consecuencias particulares.

El ciclo de vida del documento

La clase DocumentLifecycle realiza un seguimiento de nuestro progreso en la canalización de renderización. Nos permite realizar verificaciones básicas que aplican las invariantes enumeradas 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.

Durante la implementación de BlinkNG, eliminamos sistemáticamente las rutas de código que infringían estas invariantes y dispersamos muchas más aserciones en todo el código para garantizar que no se produzca una regresión.

Si alguna vez estuviste mirando el código de renderización de bajo nivel, es posible que te preguntes: "¿Cómo llegué hasta aquí?". Como se mencionó antes, hay varios puntos de entrada a la canalización de renderización. Anteriormente, se incluían 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 píxeles nuevos para la visualización o se realiza una prueba de posicionamiento para la segmentación por 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, como 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 acceso de código reentrante se quitaron o refactorizaron y ya no es posible ingresar a la canalización a partir de una fase intermedia. Esto eliminó un montón de misterio sobre cuándo y cómo se realizan las actualizaciones de renderización, lo que facilita mucho razonar sobre el comportamiento del sistema.

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

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

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

Esta lista no cambió, pero antes de BlinkNG, gran parte de este trabajo se hizo de manera ad hoc, distribuida en múltiples fases de renderización, con muchas funciones duplicadas e ineficiencias integradas. Por ejemplo, la fase de estilo siempre fue la principal responsable de calcular las propiedades de estilo finales para los nodos. Sin embargo, hubo algunos casos especiales en los que no determinamos las propiedades de estilo finales hasta que se completaba la fase de diseño. No había ningún punto formal o aplicable en el proceso de renderización en el que pudiéramos decir con certeza que la información de estilo estaba completa e inmutable.

Otro buen ejemplo de problemas anteriores a BlinkNG es la invalidación de pintura. Anteriormente, la invalidación de pintura estaba cubierta en todas las fases de renderización previas a la pintura. Cuando se modificaba el estilo o el código de diseño, resultaba difícil saber qué cambios se necesitaban en la lógica de invalidación de la pintura, y era fácil cometer un error que provocaba errores de invalidación o excesiva. Puedes leer más sobre las particularidades del antiguo sistema de invalidación de la pintura en el artículo de esta serie dedicada a LayoutNG.

El ajuste de la geometría del diseño de subpíxeles a todos los límites de píxeles para la pintura es un ejemplo de cuando teníamos varias implementaciones de la misma funcionalidad y hicimos 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 distinta cada vez que necesitábamos un cálculo único sobre la marcha de coordenadas ajustadas por píxeles fuera del código de pintura. No hace falta decir que cada implementación tenía sus propios errores, y sus resultados no siempre coincidían. Debido a que no había almacenamiento en caché de esta información, el sistema a veces realizaba el mismo procesamiento de forma repetida, otra carga en el rendimiento.

Estos son algunos proyectos importantes que eliminaron los déficits arquitectónicos de las fases de renderización antes de la pintura.

Project Squad: Canalización de la fase de estilo

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

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

Anteriormente, ComputedStyle no siempre obtenía su valor final durante el ajuste de diseño. Hubo algunas situaciones en las que ComputedStyle se actualizaba durante una fase posterior de la canalización. Project Squad refactorizó correctamente estas rutas 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. Aquí no haremos justicia a todo el proyecto, pero hay algunos aspectos notables para el proyecto BlinkNG general:

  • Anteriormente, la fase de diseño recibía un árbol de LayoutObject creado por la fase de estilo y anotaba el árbol con información sobre el tamaño y la posición. Por lo tanto, no había una separación clara entre las entradas y las salidas. LayoutNG presentó el árbol de fragmentos, que es el resultado principal de solo lectura del diseño y sirve como la entrada principal para las fases de renderización posteriores.
  • LayoutNG puso la propiedad de contención en el diseño: cuando se calcula el tamaño y la posición de un elemento LayoutObject determinado, ya no se mira fuera del subárbol con raíz en ese objeto. Toda la información necesaria para actualizar el diseño de un objeto determinado se calcula con anticipación 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 prepintura

Anteriormente, no existía una fase formal de renderización previa a la pintura, solo una bolsa de trabajo de operaciones posteriores al diseño. La fase de pintura previa surgió del reconocimiento de que había algunas funciones relacionadas que podían implementarse mejor como un recorrido sistemático del árbol de diseño después de que se completara el diseño; lo más importante:

  • Emisión de invalidaciones de pintura: Es muy difícil hacer la invalidación de pintura de forma correcta durante el proceso de diseño, cuando tenemos información 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 marca booleana simple como “posiblemente necesita invalidación de pintura”. Durante la caminata del árbol previo a la pintura, revisamos estas marcas y emitimos invalidaciones según sea necesario.
  • Generación de árboles de propiedades de pintura: Proceso que se describe con más detalle más adelante.
  • Procesamiento y grabación de ubicaciones de pintura con ajuste de píxeles: Los resultados registrados se pueden usar durante la fase de pintura y también cualquier código descendente que los necesite, sin ningún cálculo redundante.

Árboles de propiedades: geometría coherente

Los árboles de propiedades se introdujeron a principios 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 jerarquía de "capas" para representar la relación geométrica del contenido compuesto, pero eso se desmoronó rápidamente a medida que se volvían aparentes las complejidades completas de los atributos, como position:Fix. La jerarquía de capas aumentó los punteros no locales adicionales, que indicaban el "elemento superior de desplazamiento" o el "superior de clip" de una capa y, en poco tiempo, era muy difícil comprender el código.

Los árboles de propiedades corrigieron esto representando los aspectos de recorte y desplazamiento de desbordamiento del contenido por separado de todos los demás efectos visuales. Esto hizo posible modelar correctamente la verdadera estructura visual y de desplazamiento de los sitios web. A continuación, “todo” lo que hicimos fue implementar algoritmos sobre los árboles de propiedades, como la transformación del espacio de la pantalla de las capas compuestas, o determinar qué capas se desplazaban y cuáles no.

De hecho, en poco tiempo nos dimos cuenta de que había muchos otros lugares en el código donde surgían preguntas geométricas similares. (La publicación de estructuras de datos clave tiene una lista más completa). Varios de ellos tenían implementaciones duplicadas de lo mismo que hacía el código del compositor; todas tenían un subconjunto diferente de errores y ninguno de ellos modeló adecuadamente la verdadera estructura del sitio web. Luego, la solución quedó clara: centralizar todos los algoritmos de geometría en un solo lugar y refactorizar todo el código para usarlo.

A su vez, todos estos algoritmos dependen de árboles de propiedad, que son una estructura de datos clave que se usa en toda la canalización de RenderingNG. Por lo tanto, para lograr este objetivo de 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 ejecute la pintura previa antes de que pudieran ejecutarse.

Esta historia es otro aspecto más del patrón de refactorización de BlinkNG: identificar cálculos clave, refactorizar para evitar su duplicación y crear etapas de canalización bien definidas que crean las estructuras de datos que los alimentan. Calculamos los árboles de propiedades en el momento exacto en que toda la información necesaria está disponible y nos aseguramos de que los árboles de propiedades no cambien mientras se ejecutan las etapas de renderización posteriores.

Composición después de pintura: Canalización y composición

La creación de capas es el proceso de averiguar qué contenido del DOM entra en su propia capa compuesta (que, a su vez, representa una textura de GPU). Antes de RenderingNG, las capas se ejecutaban antes de la pintura, no después (consulta aquí la canalización actual; observa el cambio de orden). Primero, decidimos qué partes del DOM entrarían en cada capa compuesta y, solo entonces, dibujaríamos una lista de visualización para esas texturas. Naturalmente, las decisiones dependían de factores como los elementos del DOM que se animaban o se desplazaban, o tenían transformaciones en 3D, y qué elementos estaban pintados sobre ellos.

Esto causó problemas importantes, ya que se requería más o menos que hubiera 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 invalidate la pintura (es decir, debemos volver a dibujar la lista de visualización y, luego, generar la trama otra vez). La necesidad de invalidar puede provenir de un cambio en el DOM o de un cambio en el estilo o diseño. Pero, por supuesto, solo queremos invalidar las partes que realmente cambiaron. Eso implicó descubrir qué capas compuestas se vieron afectadas y luego invalidar una parte o la totalidad de 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 pasadas de superposición de capas (pasado: significado del marco 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 las capas, era difícil distinguir la diferencia entre las decisiones pasadas y futuras. Así que obtuvimos mucho código con razonamiento circular. Esto solía generar un código incorrecto o ilógico, o incluso fallas o problemas de seguridad, si no teníamos mucho cuidado.

Para lidiar con esta situación, antes presentamos el concepto del objeto DisableCompositingQueryAsserts. La mayoría de las veces, si el código intentara consultar decisiones pasadas de capas, causaba una falla de aserción y bloqueaba el navegador si estaba en modo de depuración. Esto nos ayudó a evitar que se introdujeran nuevos errores. Y, en cada caso en el que el código realmente necesitara consultar decisiones pasadas de capas, 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 llamada y, luego, declarar que el código era seguro y correcto. Sin embargo, descubrimos que era esencialmente imposible quitar varias llamadas, siempre y cuando la creación de capas hubiera ocurrido antes de la pintura. (Finalmente, pudimos quitarlo solo recientemente). Esta fue la primera razón que se descubrió para el proyecto Compuesto después de la pintura. Lo que aprendimos es que, incluso si tienes una fase de canalización bien definida para una operación, si está en el lugar equivocado en la canalización, finalmente te atascarás.

El segundo motivo para el proyecto Compuesto después de la pintura fue el error de composición básica. Una forma de indicar este error es que los elementos del DOM no son una buena representación 1:1 de un esquema de capas completo eficaz para el contenido de las páginas web. Y como la composición se dio antes de la pintura, dependía más o menos de los elementos del DOM y no de las listas de visualización ni de los árboles de propiedades. Esto es muy similar al motivo por el que presentamos árboles de propiedades y, al igual que con los árboles de propiedades, la solución falla directamente si determinas la fase de canalización correcta, 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 complete la fase de pintura, su resultado sea inmutable para todas las fases de canalización posteriores.

Beneficios

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

  • Fiabilidad significativamente 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 expandida de las pruebas: Durante el proceso de BlinkNG, agregamos una gran cantidad de pruebas nuevas a nuestro paquete. Esto incluye pruebas de unidades que proporcionan una verificación enfocada de los componentes internos; pruebas de regresión que nos impiden introducir errores antiguos que corregimos (¡muchos!) y muchas adiciones al conjunto de pruebas de plataforma web público, que se mantiene de forma colectiva y 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. De esta manera, es más fácil para todos agregar valor al código de renderización sin tener que ser un gran experto, y también hace que sea más fácil 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 acciones aún más grandes, como las animaciones y el desplazamiento de subprocesos universales o los procesos y subprocesos para el aislamiento de sitios sin esa canalización. El paralelismo puede ayudarnos a mejorar muchísimo el rendimiento, pero también es extremadamente complicado.
  • Rendimiento y contención: BlinkNG ofrece varias funciones nuevas que aprovechan la canalización de formas novedosas y novedosas. Por ejemplo, ¿qué pasaría si quisiéramos ejecutar la canalización de renderización solo hasta que venza un presupuesto? ¿Quieres 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 permite la propiedad content-visibility de CSS. ¿Qué hay de hacer que el estilo de un componente dependa de su diseño? Eso es consultas de contenedores.

Caso de éxito: Consultas de contenedores

Las consultas de contenedores son una próxima función muy esperada de la plataforma web (ha sido la función número uno más solicitada por los desarrolladores de CSS durante años). Si es tan genial, ¿por qué no existe todavía? Esto se debe a que la implementación de consultas de contenedores requiere una comprensión y un control muy cuidadosos de la relación entre el código de estilo y diseño. Veamos los detalles.

Una consulta de contenedor permite que los estilos que se aplican a un elemento dependan del tamaño del diseño de un elemento principal. Dado que el tamaño de diseño se calcula durante el diseño, debemos ejecutar el ajuste de estilo después del diseño, pero el repaso de estilo se ejecuta antes que el diseño. Esta paradoja de la gallina y el huevo es la razón principal por la 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 resuelven los proyectos como Combined After Paint? Lo que es peor, ¿qué pasa si los nuevos estilos cambian el tamaño del principal? ¿Esto no suele conducir a un bucle infinito?

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

Pero en realidad, eso no era suficiente y era necesario introducir un tipo de contención más débil que solo la contención de tamaño. Esto se debe a que es común que un contenedor de consultas de contenedor pueda cambiar de tamaño en una sola dirección (por lo general, bloquearlo) 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 tan larga de esa sección, no estuvo claro por mucho tiempo si era posible la contención del tamaño intercalado.

Describir la contención en un lenguaje de especificaciones abstracto es otra cosa importante, implementarla correctamente. Recuerda que uno de los objetivos de BlinkNG era aplicar el principio de contención a las caminatas en los árboles que constituyen la lógica principal de la renderización: cuando se recorre un subárbol, no se debe requerir información desde fuera del subárbol. En la actualidad (bueno, no fue exactamente un accidente), es mucho más 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 mucho más

La canalización de renderización que se muestra aquí está un poco por delante de la implementación actual de RenderingNG. Muestra las capas, como que está fuera del subproceso principal, mientras que actualmente todavía está en el subproceso principal. Sin embargo, es solo cuestión de tiempo antes de que esto suceda, ahora que se envió Composición después de la pintura y la creación de capas después de la pintura.

Para entender por qué esto es importante y a dónde nos lleva, 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 de la renderización. Como resultado, el subproceso principal suele saturarse de trabajo y la congestión del subproceso principal suele ser un 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 la época de KHTML, cuando la ejecución de un solo subproceso era el modelo de programación dominante. En el momento en que los procesadores de varios núcleos se volvieron comunes en los dispositivos de consumo, la premisa de un solo subproceso se integra por completo en Blink (antes llamado WebKit). Queríamos incorporar más subprocesos en el motor de renderización durante mucho tiempo, pero era imposible en el sistema anterior. Uno de los objetivos principales de la renderización de NG era salir de este agujero y hacer que sea posible mover el trabajo de renderización, en parte o en su totalidad, a otro subproceso (o subprocesos).

Ahora que BlinkNG se está acercando a su finalización, ya estamos empezando a explorar esta área. La confirmación sin bloqueo es una primera incursión en el cambio del modelo de subprocesos del procesador. La confirmación del compositor (o solo la 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 utilice el código de composición descendente que se ejecuta en el subproceso compositor. Mientras se lleva a cabo esta sincronización, se detiene la ejecución del subproceso principal mientras se ejecuta el código de copia en el subproceso del compositor. Esto se hace para garantizar que el subproceso principal no modifique sus datos de renderización mientras el subproceso del compositor los copie.

Confirmación sin bloqueo eliminará la necesidad de que el subproceso principal se detenga y espere a que finalice la etapa de confirmación, ya que el subproceso principal seguirá trabajando mientras se ejecuta la confirmación de forma simultánea en el subproceso del compositor. El efecto neto de la confirmación sin bloqueo será una reducción en el tiempo dedicado a renderizar el trabajo en el subproceso principal, lo que disminuirá la congestión en el subproceso principal y mejorará el rendimiento. Hasta el momento en que se redactó este documento (marzo de 2022), contamos con un prototipo funcional de un compromiso sin bloqueo y nos estamos preparando para hacer un análisis detallado de su impacto en el rendimiento.

Esperar en las alas es la composición fuera del subproceso principal, con el objetivo de hacer que el motor de renderización coincida con la ilustración moviendo la capaización fuera del subproceso principal y hacia un subproceso de trabajo. Al igual que la confirmación sin bloqueo, esto reducirá la congestión en el subproceso principal, ya que disminuye la carga de trabajo de renderización. Un proyecto como este jamás hubiera sido posible sin las mejoras arquitectónicas de Combine After Paint.

Y hay más proyectos en la canalización (juego de palabras). Por último, tenemos una base que hace posible experimentar con la redistribución del trabajo de renderización y estamos muy entusiasmados por ver lo que es posible.