27 June 2022

Олдскул по-молодежному

Недавно вышла игра "Teenage Mutant Ninja Turtles: Shredder's Revenge" у которой хватает достоинств, но сегодня мне захотелось поговорить об одном из ее недостатков. 

Расцвет жанра beat'em up пришелся на рубеж 80-х и 90-х годов прошлого века, а самые лучшие его представители, как по мне, были выпущены для 16-ти битной консоли Sega: те же "черепашки", легендарные серии Golden Axe и Streets of Rage. Это я все к тому, что когда сегодня сталкиваешься с игрой, которая во всем старается подражать классике тридцатилетней давности, то последнее, чего от нее ожидаешь это... тормозов и жестких просадок частоты кадров. В своем анализе игры Digital Foundry показывает, что на Nintendo Switch в некоторых моментах фреймрейт проседает вдвое -- ниже 30 кадров в секунду! 

Честно говоря, у меня нет слов для того, чтобы хоть как-то охарактеризовать производительность игры, поэтому вместо слов я решил использовать цифры. 

Ресурсы, которые нужны играм подобного жанра можно условно разделить на две категории -- ресурсы центрального процессора (CPU) для обслуживания игровой логики и ресурсы графического процессора (GPU) для визуализации происходящего на экране. Забавно, что спустя 30 лет тут ничего кардинально не изменилось и упомянутая выше Sega Mega Drive, точно так же содержала в себе центральный и видеопроцессор. Я не являюсь разработчиком игр, но моей двадцатилетней экспертизы в программировании хватает для того, чтобы полностью представлять себе устройство игровой логики сферического представителя beat'em up жанра. Могу утверждать, что это крайне бесхитростная штуковина, совершено не требовательная к ресурсам центрального процессора. Что подтверждается общеизвестными фактами -- игры этого жанра прекрасно работали не только на 16-ти битных консолях, но и на приставках предыдущего поколения. Центральный процессор в Mega Drive работал на частоте 7.6 МГц, поэтому, в случае Switch, разработчики получили на руки как минимум на два порядка больше вычислительных ресурсов, т.к. центральный процессор портативной приставки от N работает на частоте 1+ ГГц (при этом я еще и не учитываю тот факт, что программистам доступно целых три независимых ядра этого процессора). 

Получить какие-то оценки по графической производительности немного сложнее.
Тут я решил зайти с другой стороны и даже немного попрограммировать. Для сохранения должного градуса олдскульности, разработчики решили, что новая игра про черепашек будет работать в разрешении 480x270. Чтобы вы понимали -- это всего лишь 1/16 часть по количеству пикселей от разрешения Full HD и, кстати, именно с оглядкой на Full HD его и выбирали, т.к. отмасштабировать картинку под самое популярное нынче разрешение экрана можно просто путем увеличения каждого пикселя ровно в 4 раза по каждой стороне. В принципе, используемое разрешение не сильно далеко ушло от игр классической эпохи: Mega Drive выводила картинку с разрешением 224 пикселя по вертикали (про горизонталь тут говорить не очень корректно из-за того, что телевизоры тогда были узкоформатными)...

Итак, у нас есть картинка с разрешением 480x270 которая, как и в старые добрые времена, строится из независимых графических элементов, т.н. "спрайтов". В эпоху 8-ми и 16-ти битных приставок существовало понятие "видеопроцессора", который с частотой кадровой развертки телевизора умел выводить картинку, состоящую из некоего фона и спрайтов поверх него. Фон в данном случае -- некая подложка сцены, которую видеопроцессор собирал из маленьких графических элементов, т.н. тайлов. А потом уже поверх этого фона он "рисовал" спрайты, точно такие же графические элементы, позицией которых на экране очень легко управлять с помощью центрального процессора. Спрайты -- динамические объекты сцены, в случае beat'em up это изображение игрока и его противников. 

В наше время для визуализации двухмерных игр используется GPU, который, как мы знаем, в первую очередь разрабатывался для работы с трехмерными сценами (а не майнинга криптовалют!). 2D это частный случай 3D, поэтому никаких сложностей тут не возникает: и фон, и спрайты это текстурированные полигоны, которые всегда повернуты строго на 90 градусов к оси наблюдателя. Единственное отличие заключается в том, что уже нет нужды собрать фон из множества маленьких кусочков, его можно нарисовать в виде огромной текстуры и показывать нужную ее часть на экране. 

Текстура со множеством спрайтов одного из героев игры, в маленькой окне -- текстура одного из уровней

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

Проанализировав "тяжелые" сцены из современной игры про черепашек, когда частота кадров в ней падает вдвое, я пришел к выводу, что на экран выводится от силы 100-150 спрайтов и это будет значение с очень большим запасом, который я дал с прикидкой на крайне пессимистичное композитное изображение статусной строки для игрока (которых у нас на экране аж целых шесть штук). 

Итого мы ориентируемся на разрешение 480x270 в котором выводится фон и порядка 150 спрайтов. Насколько это серьезная нагрузка для Nintendo Switch?  

Готового ответа на такой вопрос у меня не было, но голос внутри меня сказал "ну ты же программист!" и я решил найти ответ самостоятельно в строго практической плоскости. 


Режим какой-то непонятной технической фигни: активирован. 

В написании игр я не упражнялся с 90-х, соответственно опыта работы с какими-то актуальными игровыми движками у меня нулевой. Все, что я знал, это то, что у меня взломанная приставка, а сообщество разработало, как раз для такого случая, набор библиотек libnx для доступа ко всем API приставки. 

Решил посмотреть, что это за зверь такой и, по итогу, могу сказать, что опыт взаимодействия с этим SDK был получен строго положительный. Для Windows качается онлайн установщик, который в один шаг выкачивает весь набор из компилятора, утилит и необходимых библиотек. После этого начинают компилироваться примеры, которые в большом количестве входят в стандартный комплект поставки: запускаешь make и через пару секунд на выходе получаешь готовый к загрузке на приставку nro файл... 

Тратить время на изучение родных API от Nintendo у меня не было никакого желания, тем более, что я слышал о том, можно использовать кросс платформенную библиотеку SDL. Она точно так же, в один шаг, доставляется через пакетный менеджер pacman (идет в комплекте к SDK) и еще через минуту я уже смог собрать примеры ее использующие. 

Ровно такой же позитивный опыт, какой я получил от SDK, я получил и от SDL.
Бенчмарк для спрайтов я сначала полностью написал и отладил под Windows, при этом все заработало реально с полпинка, благо, сеть завалена массой пример и туториалов по любому аспекту работы с библиотекой. Потом начал переносить все под приставку. 
Плюшка номер один -- SDK содержит достаточно свежий GCC, который без вопросов поддерживает C++17 и, например, без проблем скомпилировал мне библиотеку C++ fmt. 
Плюшка номер два -- nro файлы прекрасно работают в эмуляторе yuzu и их не нужно каждый раз закидывать на приставку, чтобы проверить результаты.
Короче, переезд кода на Switch был максимально простым, я только скорректировал одну функцию формирования путей для загрузки ресурсов, ну и добавил управление с геймпада. 

Режим какой-то непонятной технической фигни: деактивирован. 


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

Делать тест в разрешении из 90-х я посчитал несерьезным, логичным было бы посмотреть на работу в разрешении 720p -- родном разрешении при использовании приставки в портативе. Мой бенчмарк на весь экран рисует движущийся фон, а поверх него заданное количество спрайтов, каждый из которых постоянно меняет свой размер, вращается и накладывается с прозрачностью 90%. 

Видео работы бенчмарка

По итогу, в портативном режиме, в разрешении 720p (если что, это в 7 раз больше пикселей, чем в разрешении игры TMNT!) приставка легко отображает больше 1000 спрайтов с идеальной частотой 60 кадров в секунду. Тысяча спрайтов это минимум шестикратный запас от того, что нужно препарируемой нами игре. 


***

Ну и какие выводы из всего вышесказанного можно сделать?
На самом деле, ничего нового тут не скажешь: тему совершенно криворуких игроделов я поднимал уже много-много раз (к примеру или вот такое). Да и касается это не только их, но и вообще всей индустрии программирования в целом. Местный закон Мерфи тут формулируются так -- сколько аппаратных ресурсов программисту не дай, все равно он напишет поверху такое количество абстракций, пока все это не начнет тормозить совершенно неприличным образом. Ну а gamedev просто чаще всего попадается под руку, потому что там понятные и конкретные метрики оценки производительности... 
Что же касается конкретно "Teenage Mutant Ninja Turtles: Shredder's Revenge", то я на пальцах показал, что у разработчиков, даже в случае хилой Nintendo Switch, был, как минимум, двадцатикратный запас производительности, но они все равно умудрились конкретно облажаться... И, кстати, полагаю, что половину ответственности за это безобразие можно списать на подход к QA процессам. Сильно сомневаюсь, что во время разработки в игре была возможность запускать заскриптованные сценарии для автоматической оценки производительности в ситуациях типа когда у тебя на экране шесть игроков и все они одновременно применяют супер удары. В противном случае, проблема с производительностью была бы выявлена на самых ранних этапах работы и, с высокой вероятностью, исправлена. 


зы. Мой ПК с GeForce 3060 в Full HD может рендрить 32 тысячи спрайтов с частотой 60+ fps. 
Забавный факт -- SDL умеет переключаться на полностью софтварный рендринг. Так вот, в 720p мой 12600K может рисовать аж целых 64 спрайта удерживая 60 fps (разумеется, в этом тесте задействуется только одно процессорное ядро). Прозрачность спрайтов, которая вообще никак не влияет на скорость вывода GPU, в случае полностью программной отрисовки, дает возможность увеличить производительность почти в два с половиной раза. 

зы2. Криворуких разработчиков можно вычислять и по другим критериям. Например, если они валят все игровые ресурсы тысячами файлов на жесткий диск. В случае TMNT имеем россыпь из 2.5 тысяч файлов при общем размере игры в жалкие 700 Мб... Я, к примеру, никогда не видел, чтобы так делала id Software. Или там Blizzard. Хотя последних, с их подрядчиками-китайцами, в наше время вряд ли можно ставить кому-то в пример...  



No comments:

Post a Comment