Как мы в X-Ray х64 завозили

от автора

Предисловие

Доброго времени суток, речь пойдёт о игровом движке X-Ray, а точнее о его форке X-Ray Oxygen В декабре 2016 года был опубликован проект X-Ray Oxygen. Тогда я разрабатывал его один и не мечтал о том, чем он стал на данный момент.

В марте мне пришла в голову идея: «А почему бы не перенести это всё на x64?». Как вы поняли, именно об этой идее, а точнее её реализации, пойдёт речь.

Сборка проекта

Первым шагом был перенос кода, чтоб собрать всё это дело под x64 платформу. После настройки проектов я столкнулся с первой проблемой… Нет, не Ptr функции, а ассемблерные вставки…

__forceinline void fsincos( const float angle , float &sine , float &cosine )  { __asm {      fld            DWORD PTR [angle]      fsincos      mov            eax , DWORD PTR [cosine]      fstp       DWORD PTR [eax]      mov            eax , DWORD PTR [sine]      fstp       DWORD PTR [eax]  } }

Прелесть такого кода заключалась в оптимизации, но MSBuilder в x64 его не поддерживал и не поддерживает до сих пор. Большую часть такого кода можно было заменить на std аналоги, были места, которые можно было с лёгкостью поменять на Intrinsics’ы, к примеру, как:

__asm pause;

Можно было смело заменить на:

_mm_pause();

Так же в движке иногда встречались аналоги функций на нативном коде (Хвала системе CPUID). Но бывали места, от которых приходилось просто избавляться. К примеру MMX инструкции канули в лету. К счастью, они нигде и не вызывались, а просто компилировались и и валялись без дела.

Работоспособность

После всех правок по сборке наступил следующий этап: Как всё это запустить?

Первым предателем стал LuaJIT. К несчастью, LuaJIT стал нормально (ну, почти…) работать в x64 только с версии 2.0.5. И то были небольшие проблемы с аллокацией памяти из малых разрядов. Но, тогда я не знал об этом и первым делом выпилил LuaJIT и накатил ванильный Lua 5.1. Да, это исправило проблему, но скорость… Помним, скорбим. Позже мне на форуме сообщили, что можно попробовать использовать LuaJIT 2.0.4. И да, это помогло, я запустил игру и смог выйти в главное меню!

Но… Счастье было недолгим… Привет смещениям структур, типам данных и xrCDB. Игра не загружала уровень, полетели материалы на объектах и движку это сильно не нравилось. Спустя пару дней я отчаялся окончательно и решил попросить помощи у более опытного программиста под ником Giperion. Я не рассчитывал на его участие в проекте, моей мечтой был просто совет. Но, таким образом, я получил опытного разработчика в проект. С этого момента сформировалась команда.

Следующей проблемой стал OPCODE и типы данных. Пришлось переводить все udword’ы (unsigned int) на uqword’ы (unsigned long long). Только для того, чтобы понять это, пришлось провести под отладчиком около 4 часов.

Но, это было лишь частью проблемы. Настала очередь материалов. Что мы имеем:

union  {              u32            dummy;              // 4b              struct               {                  u32        material : 14;              //                    u32        suppress_shadows : 1;   //                    u32        suppress_wm : 1;        //                    u32        sector : 16;            //                }; };

Такой код в x32 спасала волшебная #pragma pack(4), но в x64 почему-то это не спасло. Пришла очередь выравнивания, путём деббага мы выяснили, что для некоторых случаев данные в структуре были валидны, а для других нет. Переделали структуру и сделали конвертер-валидатор. Структура получила следующий вид:

union    {     size_t          dummy;     struct      {         size_t      material:14;        //          size_t      suppress_shadows:1; //          size_t      suppress_wm:1;      //          size_t      sector:16;          //                  size_t      dumb : 32; // Да, это волшебный дамб в x64.      };

А валидатор был таким:

...     if (rebuildTrisRequired)     {         TRI_DEPRECATED* realT = reinterpret_cast<TRI_DEPRECATED*> (T);         for (int triIter = 0; triIter < tris_count; ++triIter)         {             TRI_DEPRECATED& oldTri = realT[triIter];             TRI& newTri = tris[triIter];             newTri = oldTri;         }     }     else     {         std::memcpy(tris, T, tris_count * sizeof(TRI));     } ...

Таким образом, пришлось поменять часть вызовов из-за флага rebuildTrisRequired, но игра смогла запуститься.

Но, со временем настала проблема с партиклами:

real_ptr = malloc( sizeof( Particle ) * ( max_particles + 1 ) ); particles = (Particle*)((DWORD)real_ptr + (64 - ((DWORD)real_ptr & 63)));

Этот код не вызывал проблем с оригинальными партиклами. Они были слишком простыми и спокойно вмещались в выделяемую для них память. Но с более сложными и красочными партиклами, которые делали модмейкерами, пришли вылеты по памяти. x64 и вылеты по памяти, как так-то?! Код был переделан, вылеты ушли:

particles = alloc<Particle>(max_particles);

Игровые проблемы

Первой проблемой стал, опять, LuaJIT

...

Полетела userdata для smart cover’ов. Эта проблема была исправлена почти самой последней. Просто переносом правок из релизнувшегося LuaJIT 2.0.5.

Следующая проблема: Физика и вычисление float’ов. control87 и _controlfp для вычисления infinity в x64 были заблокированы… Была огромная проблема с дропом предметов, один раз к трём они падали правильно. Иногда улетали в космос, иногда под террейн. Проблема крылась всего в одной переменной, которой давалось значение infinity. Ситуацию исправил FLT_MAX, одинаковый для всех платформ.

surface.mu = dInfinty // x32 surface.mu = FLT_MAX // x64

Последней проблемой стала скорость партиклов. Обратим внимание на следующий код:

DWORD angle = 0xFFFFFFFF; ... if (angle != *((DWORD*)&m.rot.x))  {     angle = *((DWORD*)&m.rot.x);     fsincos(angle, sina, cosa);     fsincos(angle, sina, cosa); }

Вроде бы всё в порядке. Но, 0xFFFFFFFF в x64 имеет другое значение, при конвертации в тип с плавающей запятой. Дело в том, что fsincos имеет Double аналог, а x64 предпочитает double данные. И это значение в double имеет значение намного больше. Ситуацию спасло преобразование в float.

DWORD angle = 0xFFFFFFFF; ... if (angle != *((DWORD*)&m.rot.x))  {     angle = *((DWORD*)&m.rot.x);     fsincos(angle, sina, cosa);     fsincos(*(float*)&angle, sina, cosa); }

Заключение

В заключение я хочу сказать всего лишь одно: порт в x64 принёс много новых знаний, которые пригодятся в дальнейшем. Я рассказал вам о многих проблемах при портировании. А дальше всё будет зависить от вас, если вы решите проделать это в каких-либо OpenSource проектах.

Спасибо за прочтение!


ссылка на оригинал статьи https://habr.com/post/421823/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *