Как я могу загрузить графические ресурсы асинхронно?

9

Давайте думать независимо от платформы: я хочу загрузить некоторые графические ресурсы во время работы остальной части игры.

В принципе, я могу загрузить реальные файлы в отдельном потоке или с помощью асинхронного ввода-вывода. Но с графическими объектами мне придется загружать их в графический процессор, а это (обычно) можно сделать только в основном потоке.

Я могу изменить свой игровой цикл, чтобы он выглядел примерно так:

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

имея отдельный поток загрузки ресурсов с диска в оперативную память.

Однако, если есть много больших ресурсов для загрузки, это может привести к тому, что я пропущу крайний срок и в конечном итоге получу пропущенные кадры. Так что я могу изменить цикл на это:

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

Эффективно загружает только один ресурс на кадр. Однако если загружать много мелких ресурсов, загрузка всех из них займет много кадров, и будет потрачено много времени.

Оптимально, я хотел бы рассчитать время загрузки следующим образом:

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

Таким образом, я только загрузил бы ресурс, если бы мог сделать это в течение времени, которое у меня есть для этого кадра. К сожалению, это требует способа оценить количество времени, которое требуется для загрузки данного ресурса, и, насколько я знаю, обычно нет способов сделать это.

Что мне здесь не хватает? Как многие игры загружают все свои вещи полностью асинхронно, без пропущенных кадров или очень долгого времени загрузки?

Панда Пижама
источник

Ответы:

7

Давайте начнем с предположения о совершенном мире. Существует два шага для загрузки ресурса: сначала вы извлекаете его с носителя и в память в правильном формате, а затем переносите его через шину памяти в видеопамять. Ни на одном из этих двух этапов фактически не нужно использовать время в главном потоке - ему нужно только принять участие в выдаче команды ввода-вывода. И ваш процессор, и графический процессор могут продолжать делать другие вещи, пока ресурс копируется. Единственный реальный используемый ресурс - это пропускная способность памяти.

Если вы используете платформу без большого уровня абстракции между вами и оборудованием, API, вероятно, раскрывает эти концепции напрямую. Но если вы работаете на ПК, вероятно, между вами и GPU сидит драйвер, и он хочет все делать по-своему. В зависимости от API вы можете создать текстуру, поддерживаемую вашей собственной памятью, но, скорее всего, вызов API «create texture» скопирует текстуру в некоторую память, которой владеет драйвер. В этом случае создание текстуры будет иметь фиксированные накладные расходы и некоторое время, пропорциональное размеру текстуры. После этого драйвер может делать что угодно - он может активно передавать текстуру в VRAM или может не загружать текстуру, пока вы не попытаетесь выполнить рендеринг с использованием ее в первый раз.

Вы можете или не можете быть в состоянии что-то с этим сделать, но вы можете сделать предположение о количестве времени, необходимого для вызова «create texture». Конечно, все числа будут меняться в зависимости от аппаратного и программного обеспечения, поэтому, вероятно, не стоит тратить кучу времени на их реинжиниринг. Так что просто попробуйте и посмотрите! Выберите показатель: «количество текстур на кадр» или «общий размер текстур на кадр», выберите квоту (скажем, 4 текстуры на кадр) и начните ее стресс-тестирование.

В патологических случаях вам может даже потребоваться отслеживать обе квоты одновременно (например, ограничение до 4 текстур на кадр или 2 МБ текстур на кадр, в зависимости от того, что меньше). Но настоящая хитрость для большинства потоков текстур состоит в том, чтобы выяснить, какие текстуры вы хотите поместить в свою ограниченную память, а не сколько времени требуется для их копирования.

Кроме того, патологические случаи для создания текстур - как много крошечных текстур, необходимых одновременно - имеют тенденцию быть патологическими случаями и для других областей. Стоит получить простую рабочую реализацию, прежде чем беспокоиться о том, сколько именно микросекунд требуется текстуре для копирования. (Кроме того, реальное снижение производительности может происходить не как время ЦП при вызове «создать текстуру», а вместо этого как время ГП в первом кадре, где вы используете текстуру.)

Джон Калсбек
источник
Это довольно хорошее объяснение. Много чего я не знал, но в нем есть смысл. Вместо стресс-тестирования я бы измерял накладные расходы на создание текстур во время выполнения, аккуратно начинал и увеличивал до 80% доступного времени выполнения, чтобы оставить место для выбросов.
Панда Пижама
@PandaPajama Я немного скептически отношусь к этому. Я ожидал бы, что устойчивое состояние будет «без копируемых текстур» и огромной дисперсии. И, как я уже сказал, я подозреваю, что часть попадания - это первый кадр рендеринга, который использует текстуру, которую гораздо сложнее измерить динамически, не влияя на производительность.
Джон Калсбек
Кроме того, вот презентация NVIDIA по асинхронной передаче текстур. Насколько я читаю, главное, что он едет домой, это то, что использование текстуры слишком скоро после загрузки может привести к остановке. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
Джон Калсбик
Я не водитель и не жокей, но так ли это? Не имеет особого смысла реализовывать драйверы таким образом, потому что при первом использовании текстуры очень вероятно, что она будет иметь пики (как в начале каждого уровня) вместо того, чтобы располагаться вдоль временной шкалы.
Panda Pajama
@PandaPajama Приложения также обычно создают больше текстур, чем доступно в VRAM, и создают текстуры, а затем никогда не используют их. Распространенным случаем является «создание набора текстур, а затем сразу нарисовать сцену, которая их использует», и в этом случае ленивость помогает драйверу, потому что он может выяснить, какие текстуры действительно используются, и этот первый кадр будет зависать в любом случае , Но я также не являюсь разработчиком драйверов, возьмите его с крошкой соли (и протестируйте!).
Джон Калсбек