Jak asynchronicznie ładować zasoby graficzne?

9

Pomyślmy bez względu na platformę: Chcę załadować trochę zasobów graficznych, gdy reszta gry jest uruchomiona.

Zasadniczo mogę ładować rzeczywiste pliki do osobnego wątku lub używając asynchronicznych operacji we / wy. Ale w przypadku obiektów graficznych będę musiał przesłać je do procesora graficznego, co można (zwykle) zrobić tylko w głównym wątku.

Mogę zmienić swoją pętlę gry, aby wyglądała mniej więcej tak:

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

mając osobny wątek ładujący zasoby z dysku na pamięć RAM.

Jednak jeśli istnieje wiele dużych zasobów do załadowania, może to spowodować, że spóźnię się z terminem ramki i ostatecznie spadną ramki. Więc mogę zmienić pętlę na to:

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

Skutecznie ładuje tylko jeden zasób na ramkę. Jednak jeśli istnieje wiele małych zasobów do załadowania, załadowanie ich wszystkich zajmie wiele ramek i będzie dużo zmarnowanego czasu.

Optymalnie chciałbym zaplanować ładowanie w następujący sposób:

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

W ten sposób załadowałbym zasób tylko wtedy, gdy mogę to zrobić w czasie, który mam dla tej ramki. Niestety wymaga to sposobu oszacowania czasu potrzebnego do załadowania danego zasobu, a o ile wiem, zwykle nie ma na to sposobów.

Czego tu brakuje? Jak wiele gier ładuje wszystkie swoje rzeczy całkowicie asynchronicznie i bez pomijania klatek lub wyjątkowo długiego czasu ładowania?

Panda Piżama
źródło

Odpowiedzi:

7

Zacznijmy od założenia idealnego świata. Ładowanie zasobu składa się z dwóch etapów: najpierw wyjmij go z nośnika pamięci i umieść w pamięci w odpowiednim formacie, a następnie przenieś go przez magistralę pamięci do pamięci wideo. Żaden z tych dwóch kroków nie musi faktycznie zajmować czasu w głównym wątku - wystarczy zaangażować się, aby wydać polecenie We / Wy. Zarówno procesor, jak i procesor graficzny mogą wykonywać inne czynności podczas kopiowania zasobu. Jedynym realnym zużywanym zasobem jest przepustowość pamięci.

Jeśli używasz platformy bez dużej warstwy abstrakcji między tobą a sprzętem, interfejs API prawdopodobnie udostępnia te koncepcje bezpośrednio. Ale jeśli korzystasz z komputera, prawdopodobnie między tobą a GPU siedzi kierowca, który chce robić to po swojemu. W zależności od API ty może być w stanie stworzyć strukturę, która jest wspierana przez pamięć, że jesteś właścicielem, ale bardziej prawdopodobne, nazywając „Utwórz teksturę” API skopiuje fakturę do jakiejś pamięci, że kierowca jest właścicielem. W takim przypadku tworzenie tekstury będzie miało pewien stały narzut i trochę czasu proporcjonalny do wielkości tekstury. Po tym sterownik może zrobić wszystko - może proaktywnie przenieść teksturę do VRAM lub może nie zawracać sobie głowy przesyłaniem tekstury, dopóki nie spróbujesz wyrenderować jej użycia po raz pierwszy.

Może lub nie może być w stanie coś z tym zrobić, ale można zrobić guesstimate kwoty czas potrzebny, aby „stworzyć strukturę” połączenia. Oczywiście wszystkie liczby zmienią się w zależności od sprzętu i oprogramowania, więc prawdopodobnie nie warto poświęcać im dużo czasu na ich inżynierię wsteczną. Więc po prostu spróbuj i zobacz! Wybierz metrykę: „liczbę tekstur na ramkę” lub „całkowity rozmiar tekstur na ramkę”, wybierz limit (powiedzmy 4 tekstury na ramkę) i rozpocznij test warunków skrajnych.

W przypadkach patologicznych może być nawet konieczne śledzenie obu przydziałów jednocześnie (np. Ograniczenie do 4 tekstur na ramkę lub 2 MB tekstur na ramkę, w zależności od tego, która wartość jest niższa). Ale prawdziwą sztuczką w przypadku większości strumieniowania tekstur jest ustalenie, które tekstury chcesz zmieścić w ograniczonej pamięci, a nie ile czasu zajmuje ich skopiowanie.

Ponadto przypadki patologiczne do tworzenia tekstur - jak wiele potrzebnych jednocześnie wielu małych tekstur - są zwykle przypadkami patologicznymi również w innych obszarach. Warto uzyskać prostą działającą implementację, zanim zaczniesz się martwić, ile dokładnie mikrosekund potrzeba na skopiowanie tekstury. (Ponadto rzeczywisty spadek wydajności może nie zostać poniesiony jako czas procesora w wywołaniu „utwórz teksturę”, ale zamiast jako czas GPU w pierwszej ramce, w której używasz tekstury.)

John Calsbeek
źródło
To całkiem dobre wytłumaczenie. Wiele rzeczy, których nie znałem, ale mają wiele sensu. Zamiast poddawać go testom warunków skrajnych, mierzyłem narzuty tworzenia tekstur w czasie wykonywania, zacząłem delikatnie i dławiłem aż do powiedzenia, 80% dostępnego czasu wykonania, aby pozostawić miejsce na wartości odstające.
Panda Pajama
@PandaPajama Jestem trochę sceptyczny. Spodziewałbym się, że stanem stałym będzie „brak kopiowania tekstur” i duża wariancja. I jak powiedziałem, podejrzewam, że część trafienia jest pierwszą ramką renderującą, która wykorzystuje teksturę, która jest znacznie trudniejsza do zmierzenia dynamicznego bez wpływu na wydajność.
John Calsbeek,
Oto prezentacja NVIDIA na temat asynchronicznych transferów tekstur. O ile czytam, kluczową rzeczą, która prowadzi do domu, jest to, że użycie tekstury zbyt wcześnie po przesłaniu spowoduje zatrzymanie. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
John Calsbeek
Nie jestem kierowcą dżokeja, ale czy to takie powszechne? Zaimplementowanie sterowników w ten sposób nie ma większego sensu, ponieważ przy pierwszym użyciu tekstur bardzo prawdopodobne jest, że pojawią się w skokach (jak na początku każdego poziomu) zamiast rozmieszczonych wzdłuż osi czasu.
Panda Pajama,
@PandaPajama Często zdarza się, że aplikacje tworzą więcej tekstur niż dostępna jest pamięć VRAM oraz tworzą tekstury i nigdy ich nie używają. Częstym przypadkiem jest „utwórz wiązkę tekstur, a następnie natychmiast narysuj scenę, która ich używa”, w którym to przypadku lenistwo pomaga kierowcy, ponieważ może dowiedzieć się, które tekstury są rzeczywiście używane, a pierwsza klatka i tak się zaczepi . Ale nie jestem też twórcą sterowników, weź to z odrobiną soli (i przetestuj!).
John Calsbeek,