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