Давайте напишем ... MMO! Часть 14: Переносимость

Опубликовано NowhereMan -

4 марта 2011

Я вернулся после недельного перерыва. Меня ненадолго навестила сестра, которая проверила, что я еще дышу, не похоронен в мусоре и не унесен пыльными кроликами. И я выбрал пару дней, чтобы поплакаться о своем прогрессе в OpenGL.

Как вы помните из последней части, я преобразовал свое демо в OpenGL 3.3 и начал портировать его на Mac OS X, только чтобы узнать, что я был введен в заблуждение OpenGL SuperBible и что Apple поддерживает только OpenGL 2.1.

Я также обнаружил, что невозможно написать фрагмент шейдера, используя шейдерный язык 3.3, который работает везде. Драйвер NVidia требует, чтобы я объявил (ранее встроенную) переменную gl_FragColor, в то время как драйверы ATI отвергают это как ошибку (или предупреждение, на некоторых драйверах.) Для протокола - NVidia следует спецификации, как я ее читал, а ATI не правы.

Это не придает мне уверенности в том, что я пишу переносимый код. В дополнение к дополнительной работе по работе с этими небольшими различиями в реализации OpenGL, как мне протестировать все это и убедиться, что оно действительно будет работать везде?

Я закончил последнюю часть, вернувшись и реализовав Texture Atlas в своей версии DirectX9. Это заменяет функцию Texture Array, которую я надеялся получить в OpenGL. Я мог бы сделать это после 11-ой части, несколько недель назад.

Я не мог смириться с мыслью о том, чтобы просто бросить всю работу OpenGL, поэтому я решил, что мне нужно попробовать еще одну платформу. Я начал портировать демо на Linux.

OpenGL на Linux

Я не делал ничего, кроме установки Apache на машину с Linux, и даже этого не делал целую вечность. Поэтому я стёр всё и установил Ubuntu 10.10. Вам нужно запросить проприетарные драйверы для дисплея. Затем мне понадобились инструменты программирования и библиотеки для написания OpenGL кода. Google указал мне на некоторые страницы со списками вещей, и я просто продолжал устанавливать пакеты до первого примера OpenGL SuperBible, скомпилированного и связанного.

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

Моя машина с Linux имеет интегрированную графику ATI, так что я подумал, что, возможно, это проблемы с видеокартой. Я подумал, что если мой код скомпилирован и, по крайней мере, работает, я всегда могу купить новую видеокарту для машины. Так что я продолжил.

Есть библиотека под названием "glut", которая должна облегчить перенос ваших OpenGL-приложений, но я заглянул внутрь нее, когда делал свою версию для Windows, и не был впечатлен. Я просто использовал ее в качестве справочника и написал свой собственный фреймворк для X Windows.

Потребовалось некоторое время, чтобы получить все системные вызовы в нужном месте, а также чтобы все это было скомпилировано и связано. Несколько месяцев назад я попробовал одну из сред разработки на C++ и мне не повезло с ней. Там было не так уж много кода для написания, так что я просто собрал свой собственный makefile и использовал стандартный редактор Ubuntu, gedit.

Через несколько дней все, казалось, заработало, поэтому я отправил результат на тестирование Флориану, и, конечно, у него он не заработал. Он также не смог его собрать.

Оказалось, что было три проблемы. Первая заключалась в том, что я не отслеживал, какие пакеты я установил, так что я не мог сказать Флориану, что ему нужно. С тех пор я начал с чистой установки (на моей машине с Windows XP, которая имеет дисплей NVidia), и у меня есть правильный список зависимостей.

Во-вторых, порядок событий, выходящих из X Windows, отличался от моего кода, и он пытался открыть окно 0 на 0. Это было легко исправить. Но третьей проблемой была еще одна неприятность. Оказалось, что в драйверах дисплея NVidia есть ошибка.

Я не виноват!

Когда программа OpenGL запускается, она запрашивает интерфейс, и может запросить любую версию OpenGL. Я запрашиваю версию 3.3. Драйвер дает мне контекст рендеринга, и вперед. Один из вызовов, который вы можете сделать, это получить список расширений OpenGL, поддерживаемых драйвером. Есть две версии, одна из которых возвращает большую длинную строку всех расширений, а другая возвращает их по одному расширению за раз:

glGetString(GL_EXTENSIONS);
glGetStringi(GL_EXTENSIONS, index);

Сначала я не понимал, насколько важен список расширений. Я намеревался просто использовать базовый стандарт и не связываться с расширениями, которые могут быть, а могут и не быть. Но оказалось, что части ядра реализованы расширениями.

Под Windows и под Linux вы используете библиотеку под названием "glew", которая просматривает все расширения и устанавливает указатели функций, вызывая драйвер дисплея. Таким образом, у вас может быть вызов типа glGenerateMipmap(), который выглядит как функция библиотеки, но это не так. Вместо этого, это макрос, скрывающий указатель, установленный функцией glewInit при запуске.

На практике это означает, что у вас есть программа с вызовом функции ядра. Она компилируется, начинает выполняться, но в glewInit она должна найти части стандарта в списке расширений. Если она их не находит, то никогда не устанавливает указатель, который вы используете при вызове функции OpenGL. Ваша программа пытается вызвать нулевой адрес и умирает.

В инициализации нет никаких предупреждений, что вы собираетесь использовать некоторую часть стандарта, которую glewInit не смог найти. Это происходило на Linux-машине Florian с дисплеем NVidia 460, но не на моей Linux-машине с интегрированной ATI-графикой.

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

На Windows вы начинаете с интерфейса OpenGL 1.1, затем получаете нужный вам интерфейс OpenGL 3.3 и перезапускаете все заново. Когда я обнаружил, что Mac поддерживает только OpenGL 2.1, я попробовал запросить интерфейс 2.1 на моей машине с Windows. Это сработало, и я подумал: "Ну, по крайней мере, я могу отладить версию 2.1 под Windows, прежде чем портировать ее на Mac". Но на самом деле с этим возникла проблема.

glewInit вызывался слишком рано, в то время как интерфейс 1.1 был запущен, и из документации это выглядело так, как будто я должен был подождать, пока не выберу правильную версию. Поэтому я немного реструктурировал код. Когда я это сделал, я обнаружил тот же самый баг NVidia под Windows.

Если вы запросите версию 3.1, все работает. Если вы запрашиваете версию 3.3, glGetString возвращает NULL, как будто расширений вообще нет. Однако, если вы используете glGetStringi, она с радостью сообщит о 195 расширениях, по одному! Очевидно, что это ошибка и ее нет у ATI. Она есть у NVidia на Windows 7, Windows XP и Linux на трех разных видеокартах. (Я сообщил об этом NVidia - пока нет ответа).

Чтобы обойти ее, мне нужно было создать свой собственный список расширений из вызовов glGetStringi и передать его в glewInit, чтобы он сделал все правильно. Я предположил, что это странно для Windows, но когда демо-версия упала по нулевому адресу на машине Флориана, я понял, что там происходит то же самое. С этим исправлением демо работало под Linux, на машине Флориана и на двух моих.

Демо все еще немного тормозит. На моем ящике с Windows XP, используя OpenGL под Windows, я получаю время рендеринга 12.72мс. На той же машине, используя OpenGL под Linux, я получаю время рендеринга 30.94мс. Я думаю, что в компиляторе у меня просто не включены правильные опции оптимизации, но я за ними не охотился.

Когда Linux заработал, пришло время еще раз попробовать Mac OS X.

OpenGL на Маке, Часть вторая

Во-первых, мне нужно было сделать OpenGL 2.1 версию моего кода. У меня было три проблемы:

  1. Текстурных массивов нет. К счастью, в конце последней части я реализовал подход текстурного атласа для DX9. Тот код подошел и для OpenGL.
  2. Существует концепция, называемая "массив вершин", который на самом деле является объектом, хранящим все ваши параметры для отрисовки вершин, так что вам не нужно каждый раз задавать их заново. Флориан указал на то, что на самом деле вам это не нужно - это просто уборка. Так что эти вызовы легко заменить.
  3. Наконец, шейдеры другие. На Mac я ограничен шейдерным языком 1.2 (не спрашивайте, почему в OpenGL 2.1 есть шейдеры 1.2...) Так как в данный момент я делаю тривиальные вещи с моими шейдерами, это просто означает изменение синтаксиса.

Я заставил все это работать под Windows. Я надеялся, что запрос интерфейса OpenGL 2.1 под glew будет означать, что все возможности, не относящиеся к версии 2.1, исчезли, но не повезло. Так как glewInit просто читает список расширений, все виды более поздних функций все равно определяются, даже если они вам не нужны. Но мне все же удалось отладить код.

Я скопировал его на свой Хакинтош и собрал с помощью XCode. Я использовал библиотеку "glut", чтобы обернуть свой код для рисования в простое приложение. И это сработало! Я увидел знакомый кусок ландшафта Minecraft на Mac! Ура!

Тогда у меня был выбор - продолжить с "glut", которому я не доверяю, или выучить Cocoa, среду, основанную на Objective-C компании Apple. Я выбрал более простой путь "glut", так как не хотел изучать новый язык программирования и конструктор интерфейсов, а также находить все вызовы библиотек для реализации своего фреймворка на другой платформе.

Как вы увидите, если вы скачаете демо-версию Mac, это была ошибка.

Я вчера поздно встал, пытаясь заставить это работать правильно, но это не так. На окне нет контрола для изменения размера, и я не могу его создать. Приложение падает при выключении (нажмите ESC), потому что нет возможности запросить выход из приложения (он должен быть прекращен пользователем через меню), а самое дерьмовое - работа с курсором.

Мой код похож на шутер от первого лица, в том, что он держит курсор в центре экрана, а не отслеживает его мышью. Я делаю это, перемещая (невидимый) указатель мыши обратно в центр каждой точки. Предполагается, что вы можете сделать это с помощью glut, но это просто не работает.

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

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

Обновление: Демо скачали всего несколько человек с Mac, и никто не сообщал, что оно не работает на Mac. Поэтому я переписал демо на Cocoa. Теперь у вас не должно быть никаких проблем с курсором.

Но подождите, тут еще! (работа...)

Итак, у меня есть версия этой демо-версии, работающая под Windows 7, Windows XP, Linux и Mac OS X. Но есть еще много работы. Если мне нужны текстовые и графические оверлеи, мне нужна 2D-графика. Я рассказал об этом для Windows в разделе третьей части. В итоге я использовал GDI для рендеринга 2D-графики в растровое изображение, затем преобразовывал растровое изображение в текстуру и рисовал ее поверх моей 3D-графики. Я могу сделать то же самое под Linux и Mac OS X, но это означает изучение API для 2D графики на обеих этих системах.

В то время комментаторы говорили, что я должен просто делать свою 2D графику с OpenGL (или DirectX) и не связываться с графическими библиотеками Windows 2D. Для простых линий, прямоугольников и изображений я мог бы это сделать. Но с текстом есть две неприятные проблемы.

Чтобы нарисовать строку с помощью OpenGL, мне нужно создать большую текстуру со всеми буквами шрифта. Затем, чтобы написать слово, я копирую части этой текстуры шрифта на треугольники на дисплее. Это означает, что я должен сделать свой собственный интервал между символами, что является проблемой.

Вы можете получить ширину любых символов под Windows (и другой ОС, я полагаю), но это не правильный способ нарисовать высококачественный текст. Хороший текст включает в себя такие вещи, как кернинг (см. часть 3) и сглаженный рендеринг. Если я нарисую свой собственный текст, я не смогу сделать все это.

Вторая проблема - азиатские языки, такие как кандзи. Там шрифт включает тысячи символов. Чтобы нарисовать их с помощью техники "copy-from-a-texture", исходная текстура должна быть огромной. Просто нарисовать ее и сохранить будет проблематично.

Сейчас я не собираюсь поддерживать Канджи в ближайшее время, так как не могу его прочитать, но мне бы не хотелось делать это невозможным по архитектурным причинам, которые не могут быть исправлены. По крайней мере, на Windows, я думаю, что вы можете рисовать строки Kanji с двухмерной графикой. Надеюсь, то же самое будет и под Mac OS X. Я не уверен насчет Linux, так как в одном из учебников XLib, которые я читал, было сказано, что широкие строки все еще не работают правильно.

Тогда я выбираю либо делать всю 2D графику с OpenGL и иметь ограниченный текст, либо изучать и переделывать 2D графику на каждой платформе (XLib для Linux и Quartz для Mac). Так как меня уже тошнит тут в сорняках, я, наверное, отложу это на потом.

Проблема в том, что переносимость не ограничивается только графикой.

В следующей версии демо мы будем двигаться по большому миру. Я хочу, чтобы демо загружало новые декорации в фоне по мере того, как вы будете двигаться. Мне понадобится несколько потоков, критические секции (блокировки) и события, чтобы сигнализировать о том, что новая работа доступна. Чтобы сохранить игру платформонезависимой, мне нужно охватить все это абстрактными классами, как я это делал для 3D графики. Также я должен узнать, как это делается под Linux и Mac OS X.

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

Итог

Ограниченная демо-версия, которая у меня сейчас есть, переносима. Она работает на следующих комбинациях ОС и видеокарт:

  • Windows 7 64-bit NVidia 250
  • Windows 7 32-bit ATI 3200
  • Windows XP 32-bit NVidia 8600
  • Linux 32-bit NVidia 8600
  • Linux 32-bit ATI 4000
  • Mac OS X 10.6.4 NVidia 8500 (Hackintosh)

Чтобы демо оставалось переносимым, мне нужна 2D-графика на других платформах. Мне также нужно сделать хороший фреймворк для Mac. Мне нужна реализация потоков и связанных с ними вещей на всех трех платформах.

Текущие Windows и Linux версии демо пытаются загрузить шейдеры NVidia, так как они, кажется, правильно реализуют спецификацию. Если компиляция шейдера не удается, он пытается загрузить ATI-версии. Для Mac всегда используются шейдеры NVidia 1.2. В дальнейшем, мне придется ограничиться тем, что я могу сделать с 1.2 шейдерами или потерять пользователей Mac.

Я не знаю, насколько хорошо мои разные машины на самом деле тестируют код. Последняя часть отчета была получена от людей, которые не смогли заставить версию Windows работать с OpenGL. Люди сообщали о проблемах с шейдерами ATI по сравнению с NVidia. Все это очень расстраивает.

Я хочу вернуться к написанию настоящей игры. В худшем случае, эта работа по переносимости дает мне представление о том, какие возможности я должен использовать. Я могу везде вернуться к OpenGL 2.1. Я просто не уверен, хочу ли я тратить время каждую неделю на отладку и тестирование на всех трех платформах.

Конечно, если бы я был мазохистом, я бы купил iPad 2 и посмотрел, смогу ли я заставить что-нибудь из этого работать там. Если бы я действительно смог создать хорошую платформу для разработки игр, которая бы работала на всех этих системах, это было бы интересно.

Это будет зависеть от вас, мои читатели! Посмотрим, сколько людей загрузит версии для Linux и Mac, и будет ли у меня много сообщений об ошибках. Если люди вернутся и скажут, что версия для Mac просто не работает на настоящих Mac, я никак не смогу это поддерживать. Если каждая версия Linux отличается и демо там не работает, я тоже не смогу это поддерживать. Если я не получу разумного успеха ни на Mac, ни на Linux, я вернусь к DirectX на Windows.

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

Демо

Большая часть интерфейса демо до сих пор отсутствует. Для перемещения используйте WASD-клавиши или стрелки. Нажмите ESC для выхода из программы.

Для Windows - качайте The Part 14 Demo - Windows.

Для Linux - качайте The Part 14 Demo - Linux.

Для Mac - качайте The Part 14 Demo - Mac.

Укажите в файле options.xml строку "platform" для выбора, какая поддержка графики используется. Версия Windows будет работать либо с "DirectX9", либо с "OpenGL3.3", либо с "OpenGL2.1". Версия для Linux будет работать с "OpenGL3.3" или "OpenGL2.1". Версия для Mac будет работать с "OpenGL2.1".

Если программа падает, то в каталоге демо вы найдете файл трассировки под названием "errors.txt". Пожалуйста, пришлите мне этот файл по адресу mgoodfel@sea-of-memes.com.

Исходники

Качайте архив The Part 14 Source с исходниками всех трех версий. В отличии от предыдущих частей, он не содержит сборки. В нем есть файлы поддержки - docs, options.xml и world.txt.