Как решить большие требования к видеопамяти в 2D-игре?

40

Как решить большие требования к видеопамяти в 2D-игре?


Мы разрабатываем 2D-игру (Factorio) на языке Allegro C / C ++, и мы сталкиваемся с проблемой увеличения требований к видеопамяти по мере увеличения игрового контента.

В настоящее время мы собираем всю информацию об изображениях, которые будут использоваться первыми, обрезаем все эти изображения в максимально возможной степени и организуем их в большие атласы настолько плотно, насколько это возможно. Эти атласы хранятся в видеопамяти, размер которой зависит от системных ограничений; в настоящее время обычно это 2 изображения размером до 8192x8192, поэтому им требуется от 256 до 512 МБ видеопамяти.

Эта система работает очень хорошо для нас, поскольку с некоторыми пользовательскими оптимизациями и разделением потока рендеринга и обновления мы можем рисовать десятки тысяч изображений на экране со скоростью 60 кадров в секунду; у нас на экране много объектов, и разрешение большого уменьшения является критическим требованием. Поскольку мы хотели бы добавить больше, возникнут некоторые проблемы с требованиями к видеопамяти, поэтому эта система не может удержаться.

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

  1. Рисование из растрового изображения памяти в растровое видео очень болезненно, в аллегро.
  2. В allegro невозможно работать с растровым изображением, отличным от основного потока, поэтому его практически невозможно использовать.

Вот некоторые дополнительные требования, которые у нас есть:

  • Игра должна быть детерминированной, поэтому проблемы с производительностью и временем загрузки никогда не могут изменить игровое состояние.
  • Игра в реальном времени и скоро станет многопользовательской. Нам нужно избегать даже самого маленького заикания любой ценой.
  • Большая часть игры представляет собой один непрерывный открытый мир.

Тест состоял из рисования 10 000 спрайтов в партии для размеров от 1x1 до 300x300, несколько раз для каждой конфигурации. Я делал тесты на Nvidia Geforce GTX 760.

  • От растрового изображения к графическому растровому изображению потребовалось 0,1 мкс на спрайт, когда исходное растровое изображение не менялось между отдельными растровыми изображениями (вариант атласа); размер не имел значения
  • Растровое изображение видео к растровому изображению видео, в то время как исходное растровое изображение переключалось между чертежами (не атласный вариант), занимало 0,56 мкс на спрайт; размер тоже не имел значения.
  • Память растрового изображения на видео растрового рисунка была действительно подозрительной. Размеры от 1x1 до 200x200 занимали 0.3us за растровое изображение, поэтому не так уж и медленно. Для больших размеров время начало увеличиваться очень резко, с 9us для 201x201 до 3116us для 291x291.

Использование атласа увеличивает производительность более чем в 5 раз. Если у меня было 10 мс для рендеринга, с атласом я ограничен 100 000 спрайтов на кадр, а без него - 20 000 спрайтов. Это было бы проблематично.

Я также пытался найти способ проверить сжатие растрового изображения и растровый формат 1bpp для теней, но я не смог найти способ сделать это в allegro.

Марвин
источник
1
Большой фанат вашей игры, я поддержал кампанию Indiegogo. Я пью на это каждые несколько месяцев. Хорошая работа до сих пор! Я удалил вопросы «какую технологию использовать», которые не относятся к теме сайта. Остальные вопросы все еще довольно широки, если у вас есть что-то более конкретное, вы должны попытаться сузить сферу.
MichaelHouse
Спасибо за поддержку. Так где же спросить, какую технологию использовать? Я не ищу ответа с конкретными рекомендациями для двигателей, но я не смог найти подробное сравнение двухмерных двигателей и их ручную проверку, включая тесты производительности и юзабилити, которые могут занять много лет.
Марвин
Проверьте в нижней части этой страницы, где можно задать такие вопросы, как «какую технологию использовать». У вас полностью обоснованный и разумный вопрос, но это не тот тип вопросов, с которым мы имеем дело на этом сайте. Даже если вы не ищете конкретный движок, это действительно единственный способ ответить на вопрос «Есть ли какая-либо технология, которая делает X?». Кто-то может просто ответить «да» и не дать рекомендации по конкретному, но это не очень поможет. Удачи с этим!
MichaelHouse
2
Вы сжимаете свои текстуры?
GuyRT
3
@Marwin, Сжатые текстуры могут работать намного лучше, чем несжатые текстуры, потому что они уменьшают необходимую пропускную способность памяти (это особенно верно для мобильных платформ, где пропускная способность намного ниже). Вы можете сэкономить огромное количество памяти, просто сжимая ваши текстуры. Действительно, единственным недостатком являются артефакты, которые неизбежно вводятся.
GuyRT

Ответы:

17

У нас похожий случай с нашей RTS (KaM Remake). Все юниты и дома являются спрайтами. У нас есть 18 000 спрайтов для юнитов, домов и местности, а также еще ~ 6 000 для командных цветов (в качестве масок). Длинно растянутые у нас также есть около 30 000 символов, используемых в шрифтах.

Итак, есть некоторые оптимизации против используемых вами атласов RGBA32:

  • Сначала разбейте пул спрайтов на множество небольших атласов и используйте их по требованию, как описано в других ответах. Это также позволяет использовать разные методы оптимизации для каждого атласа в отдельности . Я подозреваю, что у вас будет немного меньше впустую ОЗУ, потому что при упаковке с такими огромными текстурами обычно есть неиспользуемые области внизу;

  • Попробуйте использовать палитровые текстуры . Если вы используете шейдеры, вы можете «применить» палитру в коде шейдеров;

  • Возможно, вы захотите добавить опцию использования RGB5_A1 вместо RGBA8 (если, например, тени для шахматной доски подходят для вашей игры). По возможности избегайте 8-битной альфы и используйте RGB5_A1 или эквивалентные форматы с меньшей точностью (например, RGBA4), они занимают половину пространства;

  • Убедитесь, что вы плотно упаковываете спрайты в атласы (см. Алгоритмы упаковки бинов), вращайте спрайты, когда это необходимо, и посмотрите, можете ли вы перекрывать прозрачные углы для спрайтов ромба;

  • Вы можете попробовать форматы аппаратного сжатия (DXT, S3TC и т. Д.) - они могут значительно сократить использование ОЗУ, но проверить наличие артефактов сжатия - на некоторых изображениях разница может быть незаметной (вы можете использовать это выборочно, как описано в первом пункте), а на некоторых - очень произносится. Различные форматы сжатия вызывают разные артефакты, поэтому вы можете выбрать тот, который лучше всего подходит для вашего художественного стиля.

  • Обратите внимание на разделение больших спрайтов (конечно, не вручную, а в пакете атласа текстуры) на статический фоновый спрайт и меньшие спрайты для анимированных частей.

Кромстер говорит, что поддерживает Монику
источник
2
+1 для использования DXT, это очень хорошо иметь. Отличное сжатие и используется непосредственно графическим процессором, поэтому накладные расходы минимальны.
1
Я согласен с dxt. Вы также можете запросить поддержку DXT7 (аппаратное обеспечение DX11 +), размер которого совпадает с размером DXT1, но (по-видимому) более высокого качества. Однако вам нужно будет либо удвоить текстуры (одну DXT7 и одну DXT1), либо сжать / распаковать во время загрузки.
Programmdude
5

Прежде всего вам нужно использовать больше, меньшие текстурные атласы. Чем меньше у вас текстур, тем сложнее и жестче будет управление памятью. Я бы предложил размер атласа 1024, в этом случае у вас было бы 128 текстур вместо 2, или 2048, и в этом случае у вас было бы 32 текстуры, которые вы могли бы загружать и выгружать по мере необходимости.

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

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

Однако есть одна проблема: что происходит, когда происходит нечто неожиданное, чего игра не смогла предвидеть?

  • Паника и отображение экрана загрузки, пока не загрузятся все необходимые вещи. Это может быть разрушительным для опыта.
  • Спрайты с низким разрешением для всего предустановленного, продолжайте игру и заменяйте их, как только спрайты с высоким разрешением заканчивают загрузку. Это может выглядеть дешево для игрока.
  • Сделайте так, чтобы это повлияло на игровой процесс и задержало событие так долго, как это необходимо. Например, не вызывайте этого врага, пока его графика не будет загружена. Не открывайте этот сундук с сокровищами, пока не загрузится вся графика для этого лута и т. Д.
API-Beast
источник
Я добавил некоторые требования, которые я пропустил. Экран загрузки или любой вид загрузки невозможен. Все должно быть сделано в фоновом режиме, или между отдельными галочками (менее 15 мс для каждого), в то время как большую часть времени обычно уже используется для подготовки к рендерингу и обновления игры. В любом случае, разделение на более мелкие части может добавить некоторую гибкость в переключении, это будет быстрее наверняка. Вопрос в том, насколько это снижает производительность при рендеринге, поскольку переключение исходного растрового изображения во время рисования замедляет рендеринг. Я должен был бы сделать точное измерение, чтобы сказать, сколько.
Марвин
@ Марвин Влияние на производительность, да, но, поскольку вы имеете дело с 2D, вам все равно нужно быть далеко от того, чтобы это стало проблемой. Если рендеринг в настоящее время занимает 1 мс на кадр, а при использовании текстур меньшего размера это внезапно занимает 2 мс, тогда это все еще более чем достаточно для достижения согласованных 60 кадров в секунду. (16ms)
API-Beast
@Marwin Multiplayer - непростое дело, всегда было, всегда будет. Скорее всего, вам придется идти на компромиссы. Вы будете иметь заикания, просто потому, что вам придется передавать данные через Интернет, пакеты будут потеряны, пинг может внезапно увеличиться и т. Д. Заикание неизбежно, поэтому вам важнее сделать саму модель сети устойчивой к заиканию. Зная, когда ждать и как ждать других игроков.
API-Beast
Здравствуйте, заикания почти невозможно избежать в многопользовательской игре, мы сейчас работаем над этой областью, и я считаю, что у нас есть хороший план. Я мог бы даже опубликовать и ответить на свой собственный вопрос, описывающий то, что мы подробно исследовали позже :) Это может быть сюрпризом, но время рендеринга на самом деле является проблемой. Мы сделали много оптимизаций, чтобы ускорить рендеринг. Основной рендер теперь сделан в виде отдельного потока и других небольших настроек. Не забывайте, что при максимальном увеличении игрок может легко видеть десятки тысяч спрайтов одновременно. И мы даже хотели бы разрешить еще более высокие уровни масштабирования позже.
Марвин
@Marwin Хм, 10К объектов обычно не должно быть проблемой для ПК или современного ноутбука, если вы используете правильное пакетирование, профилировали ли вы свой код рендеринга?
API-Beast
2

Ого, а это, я полагаю, огромное количество анимационных спрайтов, сгенерированных из трехмерных моделей?

Вы действительно не должны делать эту игру в сыром 2D. Когда у вас фиксированная перспектива, происходит забавная вещь, вы можете легко смешивать предварительно визуализированные спрайты и фоны с живыми 3D-моделями, которые активно использовались в некоторых играх. Если вам нужны такие прекрасные анимации, это кажется самым естественным способом сделать это. Получите 3D-движок, настройте его для использования изометрической перспективы и визуализируйте объекты, для которых вы продолжаете использовать спрайты, в виде простых плоских поверхностей с изображением на них. И вы можете использовать сжатие текстур с 3D-движком, что само по себе является большим шагом вперед.

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

AAAAAAAAAAAA
источник
2

Во-первых, найдите наиболее эффективный формат текстур, который вы можете, оставаясь довольным визуальными эффектами игры, будь то RGBA4444, или сжатие DXT и т. Д. Если вас не устраивают артефакты, сгенерированные в альфа-сжатое изображение DXT, будет ли он жизнеспособным? сделать изображения непрозрачными, используя сжатие DXT1 для цвета в сочетании с 4 или 8-битной текстурой маскировки оттенков серого для альфы? Я полагаю, вы бы остаться на RGBA8888 для графического интерфейса.

Я выступаю за разделение текстур на более мелкие текстуры в любом формате, который вы выбрали. Определите элементы, которые всегда на экране и, следовательно, всегда загружены, это могут быть ландшафты и атласы GUI. Затем я бы разбил оставшиеся элементы, которые обычно отображаются вместе, как можно больше. Я не думаю, что вы потеряете слишком много производительности, даже если наберете 50-100 вызовов на ПК, но поправьте меня, если я ошибаюсь.

Следующим шагом будет создание версий этих текстур в формате mipmap, как указано выше. Я не буду хранить их в одном файле, но отдельно. Таким образом, вы получите в итоге версии 1024x1024, 512x512, 256x256 и т. Д. Каждого файла, и я буду делать это до тех пор, пока не достигну самого низкого уровня детализации, который я когда-либо хотел бы отобразить.

Теперь, когда у вас есть отдельные текстуры, вы можете создать систему уровня детализации (LOD), которая загружает текстуры для текущего уровня масштабирования и выгружает текстуры, если они не используются. Текстура не используется, если отображаемый элемент не отображается на экране или не требуется для текущего уровня масштабирования. Попробуйте загрузить текстуры в видеопамять в потоке, отдельном от потоков обновления / рендеринга. Вы можете отображать самую низкую LOD текстуру, пока не загрузите требуемую. Иногда это может привести к видимому переключению между текстурой с низкой детализацией / высокой детализацией, но я думаю, что это произойдет только тогда, когда вы выполняете чрезвычайно быстрое уменьшение и при перемещении по карте. Вы можете сделать систему интеллектуальной, попытавшись предварительно загрузить то место, куда, по вашему мнению, человек будет перемещаться, или увеличивать и загружать как можно больше в рамках текущих ограничений памяти.

Это то, что я хотел бы проверить, помогает ли это. Я полагаю, что для получения экстремальных уровней масштабирования вам неизбежно понадобится система LOD.

Кристиан
источник
1

Я считаю, что лучший подход - разделить текстуру на несколько файлов и загружать их по требованию. Возможно, ваша проблема в том, что вы пытаетесь загрузить большие текстуры, которые понадобятся вам для полноценной 3D сцены, и вы используете для этого Allegro.

Для большого уменьшения, которое вы хотите применить, вы должны использовать mipmaps. Мип-карты - это версии текстур с более низким разрешением, которые используются, когда объекты находятся достаточно далеко от камеры. Это означает, что вы можете сохранить свой 8192x8192 как 4096x4096, а затем еще 2048x2048 и т. Д., И вы переключитесь на меньшее разрешение, тем меньше вы увидите спрайт на экране. Вы можете сохранить их как отдельные текстуры или изменить их размер при загрузке (но создание мипмапов во время выполнения увеличит время загрузки вашей игры).

Надлежащая система управления будет загружать требуемые файлы по требованию и освобождать ресурсы, когда их никто не использует, плюс другие вещи. Управление ресурсами является важной темой в разработке игр, и вы сводите свое управление к простому отображению координат в единую текстуру, которая практически не имеет никакого управления.

Пабло Ариэль
источник
1
Разбивая файлы, вы имеете в виду файлы на жестком диске? Я предполагаю, что я мог бы хранить все изображения в оперативной памяти для начала, и даже копирование из растрового изображения в растровое видео слишком медленное в настоящее время, поэтому загрузка с жесткого диска, безусловно, будет еще медленнее. Наличие mimpaps мне не поможет, так как у меня все равно будет самое большое разрешение в vram.
Марвин
Да, вам не нужно загружать все, вы должны загружать только то, что вы используете. Всякий раз, когда вы хотите изменить пиксель на текстуре, загруженной в VRAM, система должна переместить ВСЮ ТЕКСТУРУ в RAM, просто для того, чтобы вы изменили один пиксель, переместите его обратно в VRAM. Если у вас есть все в одной текстуре, это предполагает перемещение 256 МБ в ОЗУ, а затем снова обратно в VRAM, что блокирует весь компьютер. Разделение его на разные файлы и текстуры является правильным способом сделать это.
Пабло Ариэль
Модификация текстуры, которая запускает копирование в память и обратно в оперативную память, применяется только для постоянных растровых изображений, кэш, вероятно, не будет установлен на постоянное, единственным недостатком будет необходимость обновить его, когда отображение будет потеряно / найдено. Но в allegro даже простое копирование изображения 640X480 из vram в память растрового изображения (сохранение предварительного просмотра игры) занимает довольно много времени.
Марвин
1
Мне нужно иметь все в одной большой текстуре, чтобы оптимизировать сам рисунок, без него эффект переключения контекста между отдельными спрайтами слишком сильно замедляет рендеринг, по крайней мере, в allegro. Не поймите меня неправильно, но вы вроде капитана, очевидно, здесь, поскольку вы смутно предлагаете мне сделать что-то, о чем я прошу в этом вопросе.
Марвин
1
Наличие этих mip-картированных текстур в разных файлах заставило бы меня перезагружать весь атлас, когда игрок увеличивает масштаб. Поскольку движок имеет всего лишь несколько единиц мс для него, я не вижу способа, как это сделать.
Марвин
0

Я рекомендую создавать больше файлов атласа, которые можно сжимать с помощью zlib и выводить из сжатия для каждого атласа, и, имея больше файлов атласа и файлов меньшего размера, вы можете ограничить объем активных данных изображения в видеопамяти. Кроме того, реализуйте механизм тройного буфера, чтобы подготовить каждый кадр рисования раньше, и у вас будет возможность завершить быстрее, чтобы заикания не появлялись на экране.

Darxval
источник