Предисловие
Доброго времени суток, речь пойдёт о игровом движке 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); }
Вроде бы всё в порядке. Но, 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 проектах.
Спасибо за прочтение!
