¿Cómo cargo los recursos gráficos de forma asincrónica?

9

Pensemos en una plataforma independiente: quiero cargar algunos recursos gráficos mientras se ejecuta el resto del juego.

En principio, puedo cargar los archivos reales en un hilo separado, o usar E / S asíncrona. Pero con los objetos gráficos, tendré que subirlos a la GPU, y eso (por lo general) solo se puede hacer en el hilo principal.

Puedo cambiar mi ciclo de juego para que se vea así:

while true do
    update()
    for each pending resource do
        load resource to gpu
    end
    draw()
end

mientras tiene un hilo separado, cargue recursos del disco a la RAM.

Sin embargo, si hay muchos recursos importantes para cargar, esto podría hacer que pierda una fecha límite de cuadros y, finalmente, se caigan los cuadros. Entonces puedo cambiar el ciclo a esto:

while true do
    update()
    if there are pending resources then
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

Efectivamente cargando solo un recurso por cuadro. Sin embargo, si hay muchos recursos pequeños para cargar, cargarlos todos tomará muchos fotogramas y habrá mucho tiempo perdido.

De manera óptima, me gustaría cronometrar mi carga de la siguiente manera:

while true do
    time_start = get_time()
    update()
    while there are pending resources then
        current_time = get_time()
        if (current_time - time_start) + time_to_load(resource) >= 1/60 then
            break
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

De esta manera, solo cargaría un recurso si puedo hacerlo dentro del tiempo que tengo para ese marco. Desafortunadamente, esto requiere una forma de estimar la cantidad de tiempo que lleva cargar un recurso dado, y que yo sepa, generalmente no hay formas de hacerlo.

¿Que me estoy perdiendo aqui? ¿Cómo pueden muchos juegos cargar todas sus cosas de forma completamente asíncrona y sin cuadros caídos o tiempos de carga extremadamente largos?

Pijama Panda
fuente

Respuestas:

7

Comencemos asumiendo un mundo perfecto. Hay dos pasos para cargar un recurso: primero lo saca de su medio de almacenamiento y lo guarda en la memoria en el formato correcto, y luego lo transfiere a través del bus de memoria a la memoria de video. Ninguno de esos dos pasos realmente necesita usar el tiempo en su hilo principal; solo necesita involucrarse para emitir un comando de E / S. Tanto su CPU como su GPU pueden seguir haciendo otras cosas mientras se copia el recurso. El único recurso real que se consume es el ancho de banda de la memoria.

Si está utilizando una plataforma sin mucha capa de abstracción entre usted y el hardware, la API probablemente expone estos conceptos directamente. Pero si está en una PC, probablemente haya un controlador entre usted y la GPU, y quiere hacer las cosas a su manera. Dependiendo de la API, es posible que pueda crear una textura respaldada por la memoria que posee, pero lo más probable es que llamar a la API "crear textura" copiará la textura en alguna memoria que posee el controlador. En ese caso, crear una textura tendrá algo de sobrecarga fija y algo de tiempo proporcional al tamaño de la textura. Después de eso, el controlador podría hacer cualquier cosa: podría transferir proactivamente la textura a VRAM, o podría no molestarse en cargar la textura hasta que intente renderizar su uso por primera vez.

Puede o no ser capaz de hacer algo al respecto, pero puede hacer una estimación aproximada de la cantidad de tiempo que lleva hacer la llamada "crear textura". Por supuesto, todos los números van a cambiar según el hardware y el software, por lo que probablemente no valga la pena invertir mucho tiempo en ingeniería inversa. ¡Así que pruébalo y verás! Elija una métrica: "número de texturas por fotograma" o "tamaño total de texturas por fotograma", elija una cuota (por ejemplo, 4 texturas por fotograma) y comience a realizar pruebas de resistencia.

En casos patológicos, incluso es posible que deba realizar un seguimiento de ambas cuotas al mismo tiempo (por ejemplo, limitar a 4 texturas por cuadro o 2 MB de texturas por cuadro, lo que sea menor). Pero el verdadero truco para la mayoría de la transmisión de texturas es descubrir qué texturas desea que quepan en su memoria limitada, no cuánto tiempo lleva copiarlas.

Además, los casos patológicos para la creación de texturas, como muchas texturas pequeñas que se necesitan a la vez, tienden a ser casos patológicos para otras áreas también. Vale la pena obtener una implementación de trabajo simple antes de preocuparse por exactamente cuántos microsegundos toma una textura para copiar. (Además, el impacto real en el rendimiento no puede incurrir como tiempo de CPU en la llamada "crear textura", sino como tiempo de GPU en el primer fotograma que usa la textura)

John Calsbeek
fuente
Esa es una muy buena explicación. Muchas cosas que no sabía pero que tienen mucho sentido. En lugar de probarlo con esfuerzo, mediría la sobrecarga de la creación de textura en tiempo de ejecución, comenzaría suavemente y aceleraría para decir, 80% del tiempo de ejecución disponible para dejar espacio para valores atípicos.
Panda Pyjama
@PandaPajama Soy un poco escéptico de eso. Esperaría que el estado estable sea "no se copien texturas" y una gran cantidad de variación. Y como dije, sospecho que parte del golpe es el primer marco de renderizado que usa la textura, que es mucho más difícil de medir dinámicamente sin afectar el rendimiento.
John Calsbeek
Además, aquí hay una presentación de NVIDIA sobre transferencias de textura asíncronas. Lo que está conduciendo a casa, por lo que estoy leyendo, es que usar una textura demasiado pronto después de subirlo se detendrá. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
John Calsbeek
No soy un conductor dev jockey, pero ¿es eso común? No tiene mucho sentido implementar controladores de esa manera, porque es muy probable que los primeros usos de la textura vengan en picos (como al comienzo de cada nivel) en lugar de estar espaciados a lo largo de la línea de tiempo.
Panda Pyjama
@PandaPajama También es común que las aplicaciones creen más texturas de las que hay disponibles VRAM, y creen texturas y luego nunca las usen. Un caso común es "crear un montón de texturas y luego dibujar inmediatamente una escena que las use", en cuyo caso ser perezoso ayuda al conductor, porque puede descubrir qué texturas se usan realmente, y ese primer cuadro se enganchará de todos modos . Pero tampoco soy un desarrollador de controladores, tómalo con un grano de sal (¡y pruébalo!).
John Calsbeek