Pull to refresh

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

Reading time4 min
Views8.3K

Предисловие


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


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

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 18: ↑17 and ↓1+16
Comments20

Articles