2 puntos por GN⁺ 2024-02-15 | 1 comentarios | Compartir por WhatsApp
  • Durante años, el M1 se quedó en OpenGL 4.1, pero los drivers más recientes para M1/M2 de Fedora Asahi Remix ahora ofrecen soporte completo para OpenGL 4.6 y OpenGL ES 3.2
  • El driver Linux de código abierto de Asahi, a diferencia del driver 4.1 no conforme del proveedor, aparece en la lista de Khronos como driver certificado por conformidad, ampliando la compatibilidad con cargas de trabajo modernas de OpenGL como Blender
  • OpenGL 4.6 exige robustness, SPIR-V, clip control, cull distance, compute shaders y transform feedback mejorado, pero el hardware M1 no encaja de forma nativa con los estándares gráficos más recientes
  • Las funciones ausentes en el hardware se resuelven con técnicas de driver y compilador: geometry shaders, tessellation y transform feedback se manejan con compute shaders, mientras que cull distance y clip control se procesan con transformaciones de shader
  • Para pasar más de 100,000 pruebas de conformidad, se implementó por software la robustness de buffers e imágenes, y se redujo el costo adicional con optimizaciones de clamp, preamble y un bypass para mipmap

OpenGL 4.6/ES 3.2 disponible en Fedora Asahi Remix

  • El M1 antes solo soportaba hasta OpenGL 4.1, pero ahora ya soporta OpenGL 4.6 y OpenGL ES 3.2
  • Los drivers más recientes de la serie M1/M2 pueden usarse instalando Fedora Asahi Remix
  • Quienes ya lo tengan instalado pueden actualizar con el siguiente comando
    • dnf upgrade --refresh
  • El driver Mesa de código abierto de Asahi, a diferencia del driver 4.1 no conforme del proveedor, cumple con la conformidad (conformance) para versiones recientes de OpenGL
  • Un driver conforme para 4.6/3.2 debe pasar más de 100,000 pruebas para garantizar exactitud
  • Hace 6 meses apareció el primer driver conforme del M1 para una API gráfica estándar con OpenGL ES 3.1, y ahora también se completó OpenGL 4.6
  • El soporte para Vulkan también está en desarrollo

Implementación de funciones para superar la barrera de OpenGL 4.1

  • OpenGL 4.6 añade varias funciones obligatorias frente a 4.1
    • Robustness
    • SPIR-V
    • Clip control
    • Cull distance
    • Compute shaders
    • Transform feedback mejorado
  • El M1 no se ajusta bien a estándares gráficos más nuevos que OpenGL ES 3.1
    • Vulkan vuelve opcionales algunas funciones, pero para montar DirectX y OpenGL encima siguen haciendo falta las funciones ausentes
    • La solución existente del M1 no podía ir más allá del conjunto de funciones de OpenGL 4.1
  • Las nuevas funciones sin soporte de hardware se implementaron con técnicas del driver
    • Geometry shaders, tessellation y transform feedback se resuelven con compute shaders
    • Cull distance se maneja con valores interpolados transformados
    • Clip control se implementa con un epílogo del vertex shader

Robustness de buffers y corrección por software en el M1

  • Tradicionalmente, los GPU priorizan el rendimiento por encima de la seguridad, así que un shader con código incorrecto que lea fuera del rango de un buffer puede provocar comportamiento indefinido
  • Para aplicaciones que manejan shaders no confiables, como los navegadores web, este compromiso no es deseable
    • La API gráfica en sí no es un límite de seguridad, así que sigue haciendo falta cierta sanitización
    • Reducir el comportamiento indefinido de la API ayuda a la defensa en profundidad
  • Al activar robustness, la aplicación puede elegir comportamiento definido para accesos fuera de rango a cambio de renunciar a algo de rendimiento
  • El resultado de una carga fuera de rango en un buffer varía según la API
    • Direct3D y Vulkan robustBufferAccess2: devuelven 0
    • OpenGL y Vulkan robustBufferAccess: devuelven 0 o algunos datos dentro del buffer
    • OpenGL ES: puede devolver un valor arbitrario, pero no debe causar un crash
  • Como OpenGL exige devolver 0 o datos dentro del buffer en accesos fuera de rango, se puede cargar con un índice seguro calculando el mínimo sin signo entre el último índice válido y el índice de acceso
    • carga de uniform buffer sin robustness: load.i32 result, buffer, index
    • después de aplicar robustness: umin idx, index, last y luego load.i32 result, buffer, idx
  • El preamble del M1 calcula una vez y reutiliza valores en lugar de repetir el mismo cálculo en todos los threads
    • Como el tamaño del uniform buffer es fijo, la aritmética adicional de robustness también puede moverse al preamble
    • Incluso en un storage buffer robusto, aunque la carga/almacenamiento en sí no se pueda mover, el cálculo del clamp sí puede ir al preamble

Implementación de robustness para vertex buffer

  • En las API gráficas, la aplicación configura la dirección base GPU del vertex buffer y el layout de atributos
    • Cada atributo tiene offset y formato
    • El buffer tiene un stride que representa los bytes por vértice
    • El vertex shader lee atributos indexándolos implícitamente con base en el vértice
  • Algunos hardwares implementan de forma nativa el robust vertex fetch o aceleran el fetch por software con buffers con verificación de límites, pero el M1 no tiene ninguno de los dos
  • Las cargas de memoria del GPU M1 reciben una dirección base de 64 bits y un offset en unidades de elemento, y también ofrecen la instrucción imad de multiplicación-suma entera
    • Una carga de atributo de 32 bits puede implementarse con dos instrucciones: imad idx, stride/4, vertex, offset/4 y load.i32 result, base, idx
    • Un atributo vectorial con 4 valores de 32 bits empaquetados de forma densa puede cargarse con una sola instrucción: load.v4i32 result, base, vertex << 2
  • La robustness requiere clamp, pero el tamaño del vertex buffer está en bytes y la carga optimizada usa un índice por vértice
  • El problema se resuelve reinterpretando múltiples atributos y offsets dentro de un mismo buffer como si fueran direcciones base separadas por atributo
    • En vez de sumar el offset en el shader, se pasa una base por atributo
    • El tamaño del buffer en bytes puede convertirse a un tamaño por atributo en unidades de vértice
    • Se hace clamp sobre el índice de vértice, no sobre el offset
  • El driver precalcula el último índice de vértice válido usando el tamaño del formato de cada atributo y se lo pasa al shader
  • Si el buffer es tan pequeño que no puede cargar nada, no basta con clamp, así que el buffer de ese atributo se reemplaza por un pequeño zero buffer
    • Como se usan direcciones base por atributo, esta decisión también puede tomarse por atributo
  • Al final, se implementa un robust vertex buffer con un poco de cálculo en el driver y el costo de un solo umin

Robustness de imágenes y bypass de mipmap

  • Además de la robustness de buffers, también hace falta image robustness, donde una carga de imagen fuera de rango debe devolver 0
  • Una imagen con mipmaps contiene múltiples niveles de detalle
    • El nivel base es la imagen original
    • Cada nivel siguiente es una versión reducida del nivel anterior
    • Al renderizar, el hardware selecciona el nivel más cercano al tamaño en pantalla para mejorar eficiencia y calidad visual
  • La especificación exige que, con robustness, una image load devuelva 0 en los siguientes casos
    • Cuando la coordenada X o Y está fuera de rango
    • Cuando el nivel está fuera de rango
  • El comportamiento de image load en el GPU M1 difiere de ese requisito
    • Si X o Y están fuera de rango, devuelve 0
    • Si el nivel está fuera de rango, devuelve el valor del último nivel
  • Como el proveedor no publica documentación del hardware, no se sabe si ese comportamiento es intencional o un bug de hardware, y para pasar la conformidad hacía falta un bypass
  • Un bypass simple sería cargar solo cuando el nivel sea válido y, si no, devolver 0 con una rama, pero las ramas son ineficientes
  • Una mejor opción es aprovechar que una carga fuera de rango no causa crash: primero se hace la carga y luego se elige 0 con compare-select
    • Pero el conjunto de instrucciones del GPU M1 es escalar, y image load devuelve un vector de 4 componentes: red/green/blue/alpha
    • Haría falta un ulesel por componente, haciendo crecer el ensamblado
  • El bypass final aprovecha que el hardware devuelve 0 cuando X o Y quedan fuera de rango
    • Como el ancho máximo de imagen es 16384 px, cambiar X a un valor como 20000 lo deja fuera de rango
    • Si el nivel es válido, se usa la X original; si no, se cambia X a 20000 para que image load devuelva 0
  • Este método compila a un ensamblado compacto porque solo cambia un escalar y no selecciona el vector completo
    • Si la constante se carga de antemano en un registro uniforme, el costo del bypass es una sola instrucción
    • Con este método se logró pasar la conformidad

1 comentarios

 
GN⁺ 2024-02-15
Opiniones de Hacker News
  • Alyssa Rosenzweig parece un gran regalo que sigue aportando a la comunidad.
    En cada entrada del blog termino aprendiendo algo que no sabía sobre la estructura interna del hardware gráfico moderno.

  • Es un trabajo que demuestra una y otra vez que la capacidad le gana a las palabras.
    Solo leer el blog ya te calienta la cabeza de tantas cosas que hay para desentrañar; y aunque la conclusión no está en la última oración sino en la segunda, al final uno termina siguiendo cada manipulación de bits hasta meterse en la madriguera.
    Si existiera un benchmark de revelaciones por párrafo, creo que Alyssa saldría primera en todos.

  • Tal vez algún día, si Apple elimina OpenGL 3.3 core, todos terminen eliminándolo también.
    En general he escuchado que OpenGL es más fácil de usar que Vulkan, pero una API demasiado compleja puede hacer que a los desarrolladores con poca experiencia les cueste aprovechar la GPU, convertirse en una barrera de entrada y expulsar a desarrolladores indie.
    Hoy en día todos usan Unity y Unreal, así que hacer algo desde cero o usar otro motor se ve raro; y aunque es interesante ver cómo el desarrollo de juegos despertó después de que Unity intentara cerrarse más, también resulta irritante.
    El open source en desarrollo de juegos siempre ha estado ajustado, y aunque existe Godot, parece difícil que compita en serio contra Unity y Unreal.
    Aunque Godot fuera lo suficientemente capaz, es probable que los desarrolladores indie estén más acostumbrados a Unity y Unreal y se queden ahí.
    El estado del open source en el desarrollo de juegos a veces se siente desesperanzador, y la llegada de las API gráficas de nueva generación no facilita las cosas.

    • Decir que OpenGL es más fácil que Vulkan es bastante razonable.
      El ejemplo de renderizado de un triángulo en OpenGL tiene unas 200 líneas, y el ejemplo de renderizado de un triángulo en Vulkan unas 1000.
    • Personalmente, creo que Metal es más fácil de usar que Vulkan.
      Vulkan está diseñado para ser muy flexible, pero a cambio no ofrece muchas comodidades.
      En cualquier caso, OpenGL era de demasiado alto nivel para exponerse como API directa del driver; encaja mejor con la forma en que funciona el hardware de GPU tener una API de bajo nivel como Vulkan como capa base y montar encima algo como OpenGL.
      Además, no todo el mundo usa Unity y Unreal.
      Los seis nominados a juego del año en The Game Awards 2023 se hicieron todos con motores propios, y en el ámbito indie todavía hay desarrolladores que crean su propio motor, como en el caso de Hades.
      Dicho eso, es cierto que la mayoría usa motores ya hechos.
    • OpenGL no está obsoleto; es más simple y se sigue usando donde Vulkan resulta excesivo.
      Si cubre todas las funciones que necesitas y puedes tolerar un pipeline de renderizado basado en estado, también es una buena opción para proyectos nuevos.
    • En macOS e iOS, OpenGL está marcado como obsoleto desde hace ya varios años.
      Todavía funciona y hoy se ejecuta como una capa sobre Metal, pero al compilar código GL para macOS o iOS aparecen advertencias de obsolescencia constantemente.
      Se pueden desactivar con un define.
    • WGPU está más cerca de intentar resolver este problema como una API multiplataforma más fácil de usar que Vulkan.
      El problema de OpenGL es que está demasiado alejado de cómo funcionan las GPU, lo que dificulta obtener buen rendimiento.
  • Me da curiosidad cuánto de este trabajo está ligado al código de la GPU M1 y cuántas implementaciones de funciones sobre otras funciones podrían reutilizarse en otros lugares.
    Se parece mucho a la forma en que Zink ejecuta funciones complejas de OpenGL sobre un Vulkan más primitivo, pero el M1 todavía no tiene un backend Vulkan al cual apuntar.

    • En términos más generales, se puede ejecutar OpenGL o Vulkan complejo sobre cualquier combinación de renderizado por software en CPU y soporte de aceleración nativa específico de hardware.
      Al final es una cuestión de cantidad de trabajo, y puede reutilizarse en hardware diverso.
      También puede ayudar a hardware antiguo y bien entendido, pero difícil de usar por sí solo con cargas de trabajo modernas.
  • Tengo muchísima curiosidad por el impacto en rendimiento que esto implica, especialmente comparado con usar Metal directamente en macOS.
    La respuesta seguramente sea “depende”, pero igual me intriga.
    Quizá la respuesta esté en el artículo, pero no entendí la mayor parte.

    • No necesariamente hay una gran diferencia entre implementar una función con código de cómputo del driver e implementarla con soporte de hardware de la GPU.
      El “soporte de hardware” también suele implementarse con microcódigo de la GPU y muchas veces pasa por el mismo silicio.
      Cualquier función puede convertirse en cuello de botella de rendimiento, y es difícil saber dónde se va a trabar hasta probarlo realmente.
    • La forma en que lo expresa Alyssa me suena algo extraña.
      Es cierto que la GPU de Apple no soporta shaders de geometría de forma nativa, pero los shaders de geometría están mal diseñados y no encajan bien con el hardware de GPU.
      Incluso en hardware que supuestamente los soporta, se sabe que son lentos, y hay una razón por la que Nvidia diseñó mesh shading.
      También se menciona mucho el transform feedback, pero la GPU de Apple puede escribir en ubicaciones arbitrarias de memoria desde cualquier etapa de shader, así que el transform feedback en la práctica es innecesario.
      El punto clave es que Apple implementó una arquitectura de cómputo concisa y recortó muchas reliquias antiguas y funciones conocidas por no funcionar bien.
      La expresión “el M1 está atrapado en OpenGL 4.1” no me parece adecuada.
      Dejé de seguir OpenGL hace mucho, así que no sé a qué funciones posteriores a 4.1 se refiere, pero me sorprendería muchísimo que hubiera algo posible en OpenGL que no se pueda hacer en Metal.
      En cambio, hay muchas cosas posibles en Metal que son totalmente imposibles en OpenGL, empezando por el hecho de que el lenguaje de sombreado de Metal tiene punteros completos.
  • Esto es para Fedora en M1
    Sería sorprendente si también se volviera posible en macOS, pero me pregunto qué haría falta para construir algo así

    • Al final, la estructura consiste en crear buffers de comandos y enviarlos a la GPU, así que hace falta una forma de hacer eso en macOS
      El driver inicial de Mesa para la GPU M1 también se arrancó enviando buffers de comandos al driver AGX de macOS mediante IOKit
      https://rosenzweig.io/blog/asahi-gpu-part-2.html
      https://github.com/AsahiLinux/gpu/blob/main/demo/iokit.c
      Así que haría falta un poco más de código de integración del lado de Mesa para pasar la surface de la GPU a algo que pueda componerse en la pantalla de macOS
    • Según los desarrolladores, en la práctica es difícil porque Apple no tiene una API pública de kernel estable: https://social.treehouse.systems/@AsahiLinux/111930744188229065
    • Me pregunto si ya podría ser posible por la ruta MoltenVK → Vulkan → Zink
    • Parece que Apple prohíbe los drivers de kernel de terceros
      Entiendo que, para crear una implementación adecuada de Vulkan u OpenGL, se necesita una contraparte del lado del kernel que se encargue del procesamiento de la GPU
      Tal vez por eso no parece haber nadie intentando implementar Vulkan nativo para macOS
      Aunque no sé si sería posible encima del driver de Apple
    • Se puede implementar un driver de OpenGL sobre Metal
      Pero no sé si tendría sentido gastar tantos recursos en una API legacy no óptima
  • Es bastante gracioso llamar robustez a cambiar los accesos fuera de rango de un trap a devolver datos arbitrarios
    La programación gráfica definitivamente es rara

    • Desde la perspectiva de escribir drivers gráficos, tiene sentido, y también encaja con la ley de Postel, es decir, el principio de robustez
      Una parte central de los drivers de GPU es hacer que aplicaciones rotas se ejecuten, o que lo hagan más rápido
      Poner valores predeterminados estrictos no va a arreglar los problemas estructurales de la industria de los videojuegos, que lanza código roto; solo hará que los usuarios se vayan
      En hardware donde las ramificaciones suelen ser muy caras, parece útil tener una bandera que le indique al sistema que maneje silenciosamente los casos límite de la forma más eficiente posible
      También parece haber muchos usos válidos en los que el programador puede estar razonablemente seguro de que esos casos límite casi no afectarán el frame renderizado final
    • Que no haya comprobaciones de robustez no significa necesariamente que un acceso fuera de rango vaya a generar un trap, así que la robustez aquí significa ofrecer un resultado conocido incluso en esos casos raros
      Si se combina con el hecho de que, en general, a las GPU no les gustan mucho los traps, tiene sentido
      Carmack también dijo alguna vez que, al diseñar megatexture, fue doloroso lograr que los fabricantes aceptaran la idea de la memoria virtual
    • Por eso, en gráficos, cómputo de alto rendimiento, física de altas energías y trading de altísima frecuencia, el futuro de C y C++ sigue siendo prometedor
      Hablar de otros lenguajes de programación en campos dominados por una cultura de “el rendimiento está por encima de la seguridad” es parecido a hablarle a una pared
  • Sin duda es algo muy interesante, pero me pregunto por qué no apuntaron primero a Vulkan
    Hoy parece un objetivo más importante, y encima ya existe una implementación de OpenGL

    • La capa de compatibilidad de OpenGL sobre Vulkan no es magia
      Para soportar una función específica de OpenGL, el driver Vulkan tiene que soportar la función correspondiente, y normalmente se necesita una extensión
      Es decir, implementar solo un driver Vulkan básico no te da gratis soporte para OGL 4.6; para que Mesa traduzca OGL 4.6 a Vulkan, hay que implementar en el driver Vulkan todas las funciones de OGL 4.6
      Además, Alyssa ya tiene experiencia en ingeniería inversa y en proyectos de drivers OpenGL
      No conozco los detalles, pero es muy probable que crear un driver para una API familiar sea mucho más fácil y rápido que crear uno para una API que no lo es
    • Primero apuntaron al viejo OpenGL e hicieron funcionar el conjunto básico de funciones
      Es muy probable que subir desde ahí hasta un OpenGL más reciente haya sido menos trabajo que una implementación completa de Vulkan, y seguramente también aprendieron mucho de lo necesario para Vulkan
    • Pensé algo parecido, pero para soportar OpenGL sobre Vulkan de todos modos se necesita una versión más alta de Vulkan y también mucho trabajo
      Así que parece que eligieron empezar por una versión baja de OpenGL para obtener más rápido algo que funcionara
  • Es bastante sorprendente pensar que, si John Carmack no se hubiera obsesionado en los 90 con usar OpenGL en Quake II, quizá OpenGL no habría tenido presencia en los juegos 3D

    • Quake probablemente sea solo una pequeña parte de la historia
      Lo que terminó convirtiendo a OpenGL en lo que fue fueron SGI y el enorme esfuerzo por crear implementaciones compatibles en múltiples sistemas y arquitecturas
    • Como dato curioso, el sitio de OpenGL más antiguo archivado tenía un enorme banner de FAST GAMES GRAPHICS, con gráficos animados de Quake 1 y otro menú :-P
      https://web.archive.org/web/19970707113513/http://www.opengl.org/
    • No sé si fue la única razón, pero que Carmack impulsara OpenGL definitivamente ayudó
      Muchas cosas relacionadas con los juegos 3D se deben a Doom y Quake
    • Unos años después, en 2011, John Carmack dijo que Direct3D era una mejor API en ese momento
      Consideraba que Microsoft había tenido el valor de seguir haciendo cambios importantes incompatibles para mejorar la API, mientras que OpenGL se vio frenado por preocupaciones de compatibilidad
      Dijo que Direct3D manejaba mejor el multithreading y que las versiones más recientes también tenían una mejor gestión de estado
      Aun así, afirmó que id Software seguía en OpenGL por inercia y que, pese a sus ventajas, no tenía planes de migrar a Direct3D
      Fuente: https://www.bit-tech.net/news/gaming/pc/carmack-directx-better-opengl/1/
    • Material de contexto: https://www.chrishecker.com/OpenGL/Press_Release