Come carico le risorse grafiche in modo asincrono?


9

Pensiamo alla piattaforma indipendente: voglio caricare alcune risorse grafiche mentre il resto del gioco è in esecuzione.

In linea di principio, posso caricare i file effettivi su un thread separato o utilizzando I / O asincrono. Ma con gli oggetti grafici, dovrò caricarli sulla GPU, e questo (di solito) può essere fatto solo sul thread principale.

Posso cambiare il mio ciclo di gioco per assomigliare a questo:

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

pur avendo un thread separato caricare risorse dal disco alla RAM.

Tuttavia, se ci sono molte grandi risorse da caricare, ciò potrebbe farmi perdere una scadenza dei frame e alla fine perdere i frame. Quindi posso cambiare il ciclo in questo:

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

Caricamento efficace di una sola risorsa per frame. Tuttavia, se ci sono molte piccole risorse da caricare, caricarle tutte richiederà molti frame e ci vorrà molto tempo perso.

In modo ottimale, vorrei programmare il mio caricamento nel modo seguente:

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

In questo modo, caricarei una risorsa solo se posso farlo entro il tempo che ho per quel frame. Sfortunatamente, questo richiede un modo per stimare il tempo necessario per caricare una determinata risorsa e, per quanto ne so, di solito non ci sono modi per farlo.

Cosa mi sto perdendo qui? In che modo molti giochi riescono a caricare tutte le loro cose in modo completamente asincrono e senza frame caduti o tempi di caricamento estremamente lunghi?

Risposte:


7

Cominciamo assumendo un mondo perfetto. Esistono due passaggi per caricare una risorsa: prima lo si estrae dal supporto di archiviazione e nella memoria nel formato corretto, e in secondo luogo lo si trasferisce attraverso il bus di memoria nella memoria video. Nessuno di questi due passaggi deve effettivamente impiegare del tempo sul thread principale: deve solo essere coinvolto per emettere un comando I / O. Sia la CPU che la GPU possono continuare a fare altre cose mentre la risorsa viene copiata. L'unica vera risorsa consumata è la larghezza di banda della memoria.

Se stai usando una piattaforma senza gran parte del livello di astrazione tra te e l'hardware, l'API probabilmente espone direttamente questi concetti. Ma se sei su un PC c'è probabilmente un driver seduto tra te e la GPU, e vuole fare le cose a modo suo. A seconda della API si può essere in grado di creare una texture che è sostenuta da memoria che si possiede, ma più probabilmente chiamando l'API "creare texture" copierà la trama in qualche memoria che il conducente possiede. In tal caso, la creazione di una trama avrà un sovraccarico fisso e un tempo proporzionale alla dimensione della trama. Dopodiché il driver potrebbe fare qualsiasi cosa: potrebbe trasferire in modo proattivo la trama su VRAM o potrebbe non disturbare il caricamento della trama fino a quando non si tenta di renderizzarla per la prima volta.

Potresti o meno essere in grado di fare qualcosa al riguardo, ma puoi fare una stima del tempo necessario per effettuare la chiamata "crea trama". Ovviamente, tutti i numeri cambieranno a seconda dell'hardware e del software, quindi probabilmente non vale la pena spendere un po 'di tempo a decodificarli. Quindi provalo e vedi! Scegli una metrica: "numero di trame per fotogramma" o "dimensione totale di trame per fotogramma", scegli una quota (diciamo, 4 trame per fotogramma) e inizia a sottoporla a stress test.

In casi patologici, potrebbe anche essere necessario tenere traccia di entrambe le quote contemporaneamente (ad esempio, limitare a 4 trame per fotogramma o 2 MB di trame per fotogramma, a seconda di quale sia inferiore). Ma il vero trucco per la maggior parte dello streaming di texture è capire quali texture vuoi inserire nella tua memoria limitata, non quanto tempo ci vuole per copiarle.

Inoltre, i casi patologici per la creazione di texture - come molte piccole trame che sono necessarie contemporaneamente - tendono ad essere casi patologici anche per altre aree. Vale la pena ottenere una semplice implementazione funzionante prima di preoccuparsi esattamente di quanti microsecondi ci vuole una texture per copiare. (Inoltre, il colpo di prestazione reale non può essere sostenuto come tempo della CPU nella chiamata "crea trama", ma invece come tempo GPU sul primo fotogramma che usi la trama.)


Questa è una spiegazione abbastanza buona. Molte cose che non sapevo ma che avevano molto senso. Invece di sottoporlo a stress test, misurerei l'overhead della creazione di texture in fase di runtime, inizierei delicatamente e accelererei per dire, l'80% del tempo di esecuzione disponibile per lasciare spazio ai valori anomali.
Panda Pajama,

@PandaPajama Sono un po 'scettico al riguardo. Mi aspetto che lo stato stazionario sia "nessuna copia da copiare" e un'enorme quantità di varianza. E come ho detto, sospetto che parte del successo sia il primo frame di rendering che utilizza la trama, che è molto più difficile da misurare in modo dinamico senza influire sulle prestazioni.
John Calsbeek,

Inoltre, ecco una presentazione NVIDIA sui trasferimenti asincroni di trame. La cosa fondamentale che sta tornando a casa, per quanto sto leggendo, è che usare una trama troppo presto dopo il caricamento si bloccherà. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
John Calsbeek,

Non sono un pilota dev jockey, ma è comune? Non ha molto senso implementare i driver in quel modo, perché è molto probabile che i primi utilizzi delle trame si verifichino in picchi (come all'inizio di ogni livello) anziché spaziati lungo la linea temporale.
Panda Pajama,

@PandaPajama È anche comune per le applicazioni creare più trame di quante non ci sia VRAM disponibile, e creare trame e non usarle mai. Un caso comune è "crea un mucchio di trame e poi disegna immediatamente una scena che li utilizza", nel qual caso essere pigro aiuta il conducente, perché può capire quali trame sono effettivamente utilizzate e quel primo fotogramma andrà comunque in trappola . Ma non sono nemmeno un pilota, lo prendo con un granello di sale (e provalo!).
John Calsbeek,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.