Хаброкаст «Заход Солнца Вручную» #1. Пытаемся настроить среду для разработки игрушки под Windows

Только что пришла в голову мысль — нужно найти какое-то хобби. Иначе с катушек можно съехать. А поскольку я весьма бесполезный человек, ничего кроме как тыкать кнопки не умеющий, хобби будет такое: не реже раза в неделю устраивать стрим с написанием игрушки. После стрима запись публикуется на Хабре. (Можно попробовать постить на Хабр прям лайв, но это сильно сложней).

Написание очень feedback driven — если кому-то нужны пояснения, то я могу пояснить как смогу. Если есть предложения — постараюсь учитывать. На хабре читаются все комментарии до последнего, в других местах — как получится.

Первый блин комом вот здесь:


Под катом — тезисное описание для тех, кому справедливо влом тратить на просмотр полтора часа.

Первый интересный аспект в том, что хочется написать всё без хрюмворков. Как обычный джавист делает? Возникла проблема — фигачишь в зависимости хрюмворк, и он тебе решает вопрос. Цена этому — из-за подобного свинства проект превращается в свинарник, и никто не может в точности определить, что же происходит. Хочется попробовать написать без этой свинофермы, чистенько и аккуратно. Возможно, это невозможно — пожалуйста, сообщите в комментариях.

Второй аспет. Я всю жизнь кодил на Java и PHP. Был опыт разработки игрушек, но только серверной части с ответом по сети, и ни разу — настоящего десктопного приложения на C++. Поэтому, по сути это будет шоу уродов — человек не разбирающийся в вопросе попробует как-то своими словами описать, что происходит.

Нет, никакого практического значения всё это не имеет. Это чтобы не съехать с катушек, и чтобы мотивировать взрослых бородатых дяденек тоже взяться за клавиатуру и запилить какую-нибудь игрушку.

К сожалению, программирование никогда не начинается с легкого и приятного развлечения. Вначале происходит период болезненного копания в настройках среды и IDE.

Первое что я понял относительно Visual Studio — это отличный инструмент для профессионала, но на новичка он производит очень фрустрирующее впечатление. После Eclipse и IntelliJ IDEA всё, знаете ли, какое-то неестественное, сиреневенький бесперспективняк с переподвыподвертом. Дело не в коем случае не в VS, дело во мне:-)

И с плагинами постоянно какая-то беда.

-ze1z8o1vfvv8l-yf4dygjwp9z0.png

Поэтому волевым решением отказываемся от Вижуалки и смотрим, что ещё есть. Vim, Emacs и прочие редакторы, не понимающие структуры исходника — отправляются лесом. Первая же попытка использовать Eclipse провалилась, поэтому инструментом был выбран CLion.

Проблема с CLion в том, что он не всё ещё не умеет полноценно работать с компилятором Visual Studio. Если попробовать сделать что-нибудь в нём, то будет вот такое:

qstwznygzmnqsvnuprpp2vsnkvi.png

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

Поэтому нужно то, что CLion умеет хорошо, и это MinGW.

Есть большая таблица версий.

7eozxslal4qzr-t_-eje2mnsp1g.png

И по ней видно, что имеет смысл смотреть только на Cygwin и Msys2. Msys2 какой-то странноватый — у него мало коммитеров, пакеты лежат на личном гитхабе одного из авторов. С другой стороны, он действительно клёвый — там свежий компилятор и куча пакетов. Это имеет решающее значение.

Установка заключается в прокликивании next-next-ok, и потом многократном выполнении команды обновления (pacman -Syu, как в Арчлинуксе) с перезапуском терминала Msys2, пока он не скажет, что всё получилось в лучшем виде.

Если просто установить Msys2 по инструкции и попробовать добавить его в CLion, не будет найдено ни одного тулчейна. Его нужно поставить самостоятельно, это делается командой pacman -S mingw-w64-x86_64-toolchain.

После установки этого пакета, CLion запускает helloworld (который генерится автоматически при создании нового проекта) и в нём работает отладка.

Во-первых, хочется понять, запустится ли вообще хоть какое-то десктопное приложение. Всё-таки, это не родной PlatformSDK, а MinGW.

Для этого подойдет незамысловатая прога, рисующая окно с помощью винапи.

И да, с MinGW она запускается. А вот с тулчейном Visual Studio оно сыпет какими-то ошибками, но с ними разбираться я не стал, потому что —, а зачем, если у нас уже работает целевая платформа?

Понятно, что для написания чего-то жизнеспособного недостаточно выводить пиксели на окне. Это тормозно, неудобно, нет никаких модных штучек. Короче, нам нужен DirectX.

Ключевой вопрос про CLion+MinGW — именно это. Если они не смогут юзать DirectX, то отправляются в помойку.

Для тестирования был нагуглен очень короткий туториал, состоящий всего из двух файлов: в одном весь код на C++, в другом — шейдер. Задача в том, чтобы запинать его работать.

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

Готовый результат лежит вот здесь GitHub. Здесь опишу, какие проблемы встретились.

Много L-строк стали просто строками. Можно поудалять буковки L. Во многих местах нужно заменить NULL на 0 чтобы не падало.

Несмотря на то, что использовался set(CMAKE_CXX_STANDARD 17), этом плане ничего интересного не произошло вообще, ничего не развалилось.

Дело, конечно, в том, что этот пример — очень древний, написанный во времена задолго до Windows 10. DirectX теперь не находится в отдельном DirectX SDK как во времена нашей неоднозначной молодости, а засунут прямо в Windows SDK.

Поэтому первое что нужно сделать — запустить установщик Visual Studio и проверить, что установлена свежая версия Windows SDK.

Второй вопрос — в заголовках.

#include 
#include 
#include 

Их больше нет, нужно что-то вроде:

#include 
#include 
#include 
#include 

И потом в CMakeLists добавить поиск до них в конец файла:

set(LIBS d3d9 d3d11 d3dcompiler_43)
target_link_libraries(src ${LIBS})

Раньше была вот такая структура:

typedef struct D3DXCOLOR {
  FLOAT r;
  FLOAT g;
  FLOAT b;
  FLOAT a;
} D3DXCOLOR, *LPD3DXCOLOR;

И больше её нет. Впрочем, во всех местах, где она реально была нужна, получилось заменить D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f) на {0.0f, 0.2f, 0.4f, 1.0f}.

Проблема интересней оказалась с D3DX11CompileFromFile. Её больше нет!

В статье Living without D3DX её предложили заменить на D3DCompileFromFile.

isfgji5yhbnbmrti5kspe3dpfsm.png

Но вот проблемка, D3DCompileFromFile у нас тоже почему-то недоступно!

Небольшое расследование показало, что документация Microsoft предлагает воспользоваться очень новой версией API:

0-qfteyli_cyyfbjnnbuedmkgza.png

А MinGW отправляет нас в прошлое на несколько лет:

rrjhzmmraelyvknnqoudt74okm4.png

Впрочем, в 32-битной версии есть более новый заголовок, но как его присобачить к 64-битам я не разобрался. 32 бита на, наверное, на фиг не сдались.

С одной стороны, это очень печально, потому что предвещает гемор в отношениях с MinGW в дальнейшем. Интересно, кто мантейнеры всех этих дел.

С другой стороны, если взять отсутствующую D3DCompile и присутствующую D3DCompileFromFile:

HRESULT WINAPI D3DCompileFromFile(
  in      LPCWSTR pFileName,
  in_opt  const D3D_SHADER_MACRO pDefines,
  in_opt  ID3DInclude pInclude,
  in      LPCSTR pEntrypoint,
  in      LPCSTR pTarget,
  in      UINT Flags1,
  in      UINT Flags2,
  out     ID3DBlob ppCode,
  out_opt ID3DBlob ppErrorMsgs
);
HRESULT WINAPI D3DCompile(
  in      LPCVOID pSrcData,
  in      SIZE_T SrcDataSize,
  in_opt  LPCSTR pSourceName,
  in_opt  const D3D_SHADER_MACRO pDefines,
  in_opt  ID3DInclude pInclude,
  in_opt  LPCSTR pEntrypoint,
  in      LPCSTR pTarget,
  in      UINT Flags1,
  in      UINT Flags2,
  out     ID3DBlob ppCode,
  out_opt ID3DBlob ppErrorMsgs
);

то окажется, что там разница только в следующем:

  in      LPCWSTR pFileName,

против

  in      LPCVOID pSrcData,
  in      SIZE_T SrcDataSize,
  in_opt  LPCSTR pSourceName,

К сожалению, я не знаю C++, поэтому наговнокодил как умел: просто добавил вычитывание файла и редирект этих данных в D3DCompile.

void WINAPI D3DCompileFromFile(const char *filename,
                       const D3D_SHADER_MACRO *defines, ID3DInclude *include, const char *entrypoint,
                       const char *target, UINT sflags, UINT eflags, ID3DBlob **shader, ID3DBlob **error_messages) {

    SIZE_T data_size;
    char* buffer;

    ifstream infile;
    infile.open(filename, ios::binary);
    infile.seekg(0, ios::end);
    data_size = infile.tellg();
    infile.seekg(0, ios::beg);
    buffer = new char[data_size];
    infile.read(buffer, data_size);
    infile.close();

    D3DCompile(buffer, data_size,filename,
                           defines, include,entrypoint,
                           target, sflags, eflags, shader, error_messages);
}

Единственное важное различие между почившим в тьме веков D3DX11CompileFromFile и нашим самопальным D3DCompileFromFile — в отсутствии в новом API ID3DX11ThreadPump в качестве параметра. Это что-то для асинхонности, возможно какой-то тредпул? Впрочем, в туториале он и не использовался, там на его месте стоит 0.

Связка DirectX + MinGW + Msys2 + CLion является достаточно жизнеспособной, чтобы запилить простую игру. Есть возможность не только использовать базовое винапи, но и рисовать, и даже с шейдерами.

В общем-то, всё. Напоминаю, что нехитрый результат лежит на GitHub.

Пожалуйста, помните, что это не мой код, а модифицированный текст из туториала — впрочем я надеюсь, что такое его использование является честным и целевым. Но тем не менее, именно поэтому там нет никой нормальной лицензии.

ji3h7j7vvgdtvzfyn1duwiopfwo.png

Пожалуйста, пишите в комментариях ваши замечания и предложения. Ради этого всё и делается, послушать что вы скажете. Особенно если вы — матёрый игропрограммист, прямо зубр, бизон, и не помещаешься в тред по размерам. Обязательно найдо зайти и что-нибудь написать.

Напоминаю, что я, как и любой дргой блоггер, питаюсь лайками и дизайками. Чем яростней вы будете наяривать стрелку вверх под этим постом, тем более вероятно выйдет следующий пост и стрим.

олег родился в интернете
в почтенной геймерской семье
питался лайками и в гугол
за двойки ставили его

© Александр Раевский

© Habrahabr.ru