Разработка игрового 3D-движка Force Tech: загадочные тормоза

Прошедший месяц разработки прошел под эгидой исследования причин странных задержек при отрисовке кадра в моём движке Force Tech.

Чтобы читать было интереснее, предлагаю посмотреть на скриншоты из предыдущей версии Force Tech. FM для Thief II - Reverse Robbery Director's Cut.
Чтобы читать было интереснее, предлагаю посмотреть на скриншоты из предыдущей версии Force Tech. FM для Thief II - Reverse Robbery Director's Cut.

В какой-то момент я заметил, что от былых достижений в скорости отрисовки не осталось и следа: полтора миллиона полигонов выводились не в 90fps (~11ms), а в 30. Сначала я подумал, что что-то сломал. Долго всё перепроверял, оптимизировал, улучшал, в итоге подобными ухищрениями удалось ускорить отрисовку до 40 fps (~20ms), но это всё равно было в два раза медленнее прежнего результата. Чуть позже я случайно обнаружил, что былая скорость возвращается, если запустить приложение сразу после перезагрузки компьютера, но если сделать это хотя бы через пару минут простоя, тормоза снова одолевают.

Сначала думал, что причина — некий фоновый паразитный процесс, долго его искал, убивая в диспетчере задач всё что казалось подозрительным и не очень. В результате пришел к выводу, что дело не в процессе, а скорее в памяти. Мой движок начинал нормально работать, если суммарная занятая оперативная память в системе не превышала 4 Гигабайт. Это казалось очень странным, ведь, как минимум, раньше такого не было.

В профилировщике сравнил два варианта работоспособности (нормальный и тормозной) и увидел там странные задержки в видеодрайвере. Спросил умных ребят на форуме gamedev.ru, сначала думали на синхронизацию потоков CPU, но запуск даже в одном потоке не дал улучшений. Потом я по рекомендации решил уменьшить размер атласа текстур с 8k до 4k и произошло долгожданное чудо — скорость вернулась! Но было непонятно, почему. Принялся выяснять, для этого сделал то, о чём давно мечтал — массив текстурных атласов в видеопамяти. Оказалось, что при выделении определенного количества слоёв атласа в видеопамяти, всё нормально, но стоило выделить на один больше — начинались тормоза.

Thief Gold FM - A Thief's Training
Thief Gold FM - A Thief's Training

Виновницей подобных задержек была назначена нехватка видеопамяти. Если честно, я думал, что при выделении слишком большого объема видеопамяти драйвер видеокарты сообщит, что эта самая память закончилась, мол увы, приложение завершает работу. Или нечто подобное. Всё оказалось хитрее: при нехватке видеопамяти, драйвер прозрачно начинает пользоваться оперативной, а это приводит к синхронизации видеокарты и CPU, что означает постоянные простои обоих устройств в ожидании друг друга — тормоза.

Пользуясь случаем, хочу поблагодарить всех причастных ребят с форума gamedev.ru, в особенности, пользователя Aroch! Спасибо!

"Но, почему видеопамять так быстро закончилась?" — спросите вы. Такой же вопрос возник и у участников форума. На самом деле, я же делаю "стриминг всего", геометрии и текстур, для этого в видеопамяти в начале разработки умозрительно выделил по 128/256 Мб для каждого из буферов (всего их аж 10 штук). Но когда решил подсчитать, сколько получилось суммарно, понял что почти приблизился к лимиту своей видеокарты в 2Гб. Вооружившись пониманием происходящего, изменил лимиты таким образом, чтобы не выходить за пределы Гигабайта видеопамяти, и при этом оставался бы разумный запас для каждого буфера.

"Тогда надо получить всю имеющуюся свободную видеопамять и выделить её приложению!" — подумал я, — "чтобы не вылезать за её пределы и у пользователей не начинались необъяснимые тормоза!" Но мой пыл быстро угас, когда после продолжительных поисков и исследований так и не нашлось кроссплатформенного достоверного способа узнать доступные объемы памяти видеокарты. Да, есть монструозные по объемам, либо же платные библиотеки, которые умеют такое делать, но я решил сделать это одной из настроек программы, специфический «ползунок качества картинки».

Казалось бы, вот и сказке конец, да в ней намёк, который добрые молодцы могут смело мотать на ус... Но я в тот момент настолько приблизился к реализации ещё одной своей мечты — сжатых в DXT атласов, что решил "а почему бы и нет?" Провёл ещё некоторое время в исследованиях и экспериментах — и вуаля! Занимаемые текстурными атласами объемы памяти сократились вчетверо, а скорость отрисовки даже выросла на ~1-2ms!

После проведенной серии оптимизаций и рефакторингов результаты отрисовки следующие (для тестов я использую бюджетную видеокарту Radeon RX550):
~4.6 млн текстурированных полигонов при 60 fps,
~9.3 млн — при 30 fps.
Зависимость линейная. Сейчас объемы всех буферов не превышают 800 Мегабайт (но это с большим запасом) . Так что даже видеокарты с объемом видеопамяти в Гигабайт будут в деле 😉.

69
47 комментариев