Предисловие


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


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

Комментарии (18)


  1. DarkGiperion
    30.08.2018 21:26

    Хочу уточнить по поводу CDB::TRI_DEPRECATED.
    Дело в том, что dummy внутри использовался как указатель в игре и как структура в компиляторе. Положение ухудшалось тем, что она записывалась внутри бинарного файла уровня. Серьёзно изменить структуру нельзя — иначе ломалась совместимость с старыми уровнями (которые были не только от GSC, но и от мододелов).


  1. shevyrinn
    30.08.2018 21:26

    Годно, годно, интересно


  1. AlexTOPMAN
    31.08.2018 01:22

    Ставил у себя и проверял. Отличная работа.


  1. 0xf0a00
    31.08.2018 10:24

    Может кто нибудь знает ответ почему на топовых машинах xray фризит?


    1. vertver
      31.08.2018 12:24

      В основном это связанно с тем, что X-Ray очень часто обращается к жесткому диску, поэтому во время игры могут замечаться микрофризы. Также ещё возникают фризы на 2-3 секунды из-за ALife (искусственный интеллект), который выполняется на Main Thread.


      1. AlexTOPMAN
        31.08.2018 14:40

        А поставить рамдрайв или рамкеш для разгрузки накопителя в случае его насилования — не все, я смотрю, догадались? ;)


    1. vowantuz
      31.08.2018 13:26

      Старый Xray сугубо 32-х битный и не умеет в многопоточность. Это приводит к тому, что повышение производительности даёт больше всего производительность на ядро, особенно частота, и происходит упираение в 2гб максимальной используемой оперативной памяти. Графика, в прочем, не имеет столь серьёзных ограничений, только отсутствие использование технологий после года, примерно, 10-го.

      Тут тебе совет — поставь патч на задействование 4гб оперативы, на улучшение работы с многопоточными\многоядерными системами и поищи патчи на исправления ошибок от сообщества.

      Продолжая историческую справку…

      Однако, фанатские модификации тут вносят коррективы. Во первых есть патч для поддержки максимального доступного 32-х битного объема оперативной памяти (именно того, который сможет задействовать игра) в почти 4гб. А так-же улучшения по части графики. Но до 2014 года это был по сути предел, модификации добавляющие большое количество контента вызывали падение производительности уже из за невозможности игры задействовать дополнительные ресурсы по части процесорной мощности и оперативной памяти. Это было неприятно, да.
      С выходом исходного кода xray появились довольно глобальные модификации, которые позволили задействовать несколько ядер процессора (я не знаю подробностей того как это реализовано) и в целом корректировок кода игры. Тут модификации стали работать лучше. Ну и сейчас есть несколько проектов которые по объёму превышают любую из игр серии (в т.ч. которые их компилируют вместе с добавлением контента).
      Описал довольно поверхностно.


      1. ForserX Автор
        31.08.2018 13:27

        По поводу многоядерности: GSC залочили все потоки на 1 ядро, чтоб на WinXP проблем не было. Достаточно было поправить тредпул и main функцию.


        1. AlexTOPMAN
          31.08.2018 14:33

          Но это же вы про многоядерность, а не про многопоточность, которая большинство и интересует. ;)


          1. ForserX Автор
            31.08.2018 17:44

            В окси добавлена ещё один поток для апдейта. В разработке рендер поток.


            1. AlexTOPMAN
              31.08.2018 21:26

              За это отдельное спасибо.


      1. 0xf0a00
        31.08.2018 13:30

        Спасибо за понятное объяснение


  1. Peacemaker
    31.08.2018 11:14

    А можно немного пояснить для тех, кто не в теме, это теперь можно в «Сталкера» поиграть с меньшим количеством лагов и бОльшим FPS?


    1. ForserX Автор
      31.08.2018 13:28

      За FPS не ручаюсь. Новая графика его не щадит.


    1. AlexTOPMAN
      31.08.2018 14:37

      Про фпс я бы сказал так: у кого разгон ЦПУ динамический (не лочили), то есть шанс не кочегарить только 1 ядро со снижением его частоты от высокого нагрева, т.к. распределение нагрузки по разным ядрам не позволит им предельно разогреться, а значит предельную частоту они будут держать. Значит должны стабильнее держать и высокий фпс. (очень примерное пояснение)


  1. rememberharumamburu
    31.08.2018 12:24

    Респект и Уважение вам ребята! Последний раз проходил всю трилогию года 2 назад и отметил что производительность на моем ПК, с процом FX 8350, GTX 1060 3Gb и 16 гигами оперативы НЕ АХТИ уж совсем… Теперь есть повод погрузиться заново, побегать по знакомым местам без тормозов!


  1. JohnDoe_71Rus
    31.08.2018 14:54

    X-Ray еще и на линукс тянут. Вот тут идет основная работа над Linux-портом.


    1. ForserX Автор
      31.08.2018 17:45

      И постоянные сообщения на сервере: подчините сборку под Винду/Линукс