Logo

Linux, OpenGL и FreePascal: Часть Первая.
29.12.07

Не буду петь хвалебные оды такой прекрасной ОС как GNU/Linux, и перейду непосредственно к делу. Также в статье не буду оперировать серьезной терминологией, а попытаюсь описать процесс создания простейшего OpenGL-приложения в Linux простым языком, не забывая про сопутствующие мелочи :).

Думаю "чистых" линуксоидов, как "чистых" людей в Зеоне :) Поэтому позволю себе некоторые сравнения с MS-поделкой, т.к. именно для нее начал писать свои первые OpenGL-приложения.

Как и в Windows, в Linux схема инициализации OpenGL-приложения включает такие элементы как:

  • Создание окна
  • Выбор формата пикселей
  • Создание контекста OpenGL

Но из-за клиент-серверной архитектуры X Window, в Linux существует еще один пункт - "Соединение с X Server". Также отличительной особенностью есть то, что формат пикселей выбирается независимо от окна, и нет такой привязки как в случаи с окнами в Windows(т.к. именно по их хэндлу получается Device Context). Тут даже наоборот - окно зависит от выбранного формата пикселей(если создавать opengl-приложения). Поэтому схема инициализации OpenGL-приложения будет иметь следующий вид:

  • Соединение с X Server
  • Выбор формата пикселей
  • Создание окна
  • Создание контекста OpenGL

Перейду к рассмотрению каждого пункта в отдельности.

Соединение с X Server

Это делается весьма просто:

  scr_Display := XOpenDisplay( nil );
  if not Assigned( scr_Display ) Then
    begin
      // Ошибка
      exit;
    end;
  scr_Default := DefaultScreen( scr_Display );

Функция XOpenDisplay возвращает указатель на структуру TDisplay, что несет в себе всю информацию об X Server'е. Т.к. связь с X Server происходит без участия сети, то в качестве параметра следует передать nil(более детально можно прочитать набрав в консоле "man XOpenDisplay"). В итоге полученный ответ запишется в scr_Display(переменная типа PDisplay), которая будет служить главным средством в общении с X Window. DefaultScreen, это макрос, возвращающий идентификатором на текущий "экран"(Display), т.е. на тот что использует запущенная x-сессия. Этот идентификатор также потребуется в будущем.

Выбор формата пикселей

Выбор формата пикселей происходит посредством расширения X Window - GLX, и ее функции glXChooseVisual, куда передаются переменные scr_Display, scr_Default и массив атрибутов.

  ogl_Attr[ 0 ] := GLX_RGBA;
  ogl_Attr[ 1 ] := GLX_DOUBLEBUFFER;
  ogl_Attr[ 2 ] := GLX_DEPTH_SIZE;
  ogl_Attr[ 3 ] := 24;
  ogl_Attr[ 4 ] := GLX_STENCIL_SIZE;
  ogl_Attr[ 5 ] := 8;
  ogl_Attr[ 6 ] := None;

  ogl_VisualInfo := glXChooseVisual( scr_Display,
                                     scr_Default,
                                     @ogl_Attr );
  if not Assigned( ogl_VisualInfo ) Then
    begin
      // Ошибка
      exit;
    end;

ogl_Attr - массив из элементов типа DWORD, который заполняется атрибутами формата пикселей. GLX_RGBA - использование формата пикселей с альфа-каналом, GLX_DOUBLEBUFFER - использовать двойную буферизацию и т.д. Думаю тем кто дружит с английским и/или хорошо знаком с OpenGL пояснять значения этих констант не требуется, во всяком случаи с их детальным описание и полным перечнем можно ознакомиться используя уже упоминавшуюся команду man передав ей glXChooseVisual.

Особенностью заполнения массива параметров есть то, что после некоторых констант требуется задать дополнительное значение. Например чтоб задать размер z-буфера(GLX_DEPTH_SIZE), второму элементу массива было присвоено соответствующую константу, а следующему элементу - размер в 24 бита. Чтобы дать понять функции что перечень параметров закончен, следует одному из элементов массива присвоить значение 0(или константа None). Хотя думаю это все знакомо тем кто использовал wglChoosePixelFormatARB.

Создание окна

Пожалуй это самый "трудоемкий" этап :). И начинается он с заполнения атрибутов будущего окна. Эти атрибуты описываются структурой TXSetWindowAttributes, из них нас пока будут интересовать лишь два - colormap и event_mask. Первый обязательный, и заполняется colormap'ом(картой цвета?) полученным из root-окна(по сути десктоп пользователя, который будет служить как parent для создаваемых окон), а второй формирует маску обработки сообщений. Также следует не забыть сформировать маску, которая будет описывать какие из заполненных атрибутов следует учитывать при создании окна:

  // Получаем идентификатор root-окна
  wnd_Root := RootWindow( scr_Display, ogl_VisualInfo^.screen );
  // Создаем colormap
  scr_ColorMap := XCreateColormap( scr_Display,
                                   wnd_Root,
                                   ogl_VisualInfo^.visual,
                                   AllocNone );

  wnd_Attr.colormap   := scr_ColorMap;
  wnd_Attr.event_mask := ExposureMask;
  wnd_ValueMask       := CWColormap or CWEventMask or CWX or CWY;

Маску wnd_ValueMask формируют два дополнительных значения(CWColormap и CWEventMask указывают на colormap и event_mask) - CWX и CWY, они позволят задавать окну координаты относительно верхнего левого угла экрана. Т.к. рассмотрение процесса обработки сообщений в данной части не планируется, то описание wnd_Attr.event_mask опущу. Теперь собственно создание окна:

  wnd_Handle := XCreateWindow( scr_Display,
                               wnd_Root,
                               X, Y,// X, Y окна
                               Width, Height,// Ширина/Высота окна
                               0, // Ширина рамки
                               ogl_VisualInfo^.depth,
                               InputOutput, // Окно будет на ввод/вывод
                               ogl_VisualInfo^.visual,
                               wnd_ValueMask,
                               @wnd_Attr );

  if wnd_Handle = 0 Then
    begin
      // Ошибка
      exit;
    end;

  XMapRaised( scr_Display, wnd_Handle );
  glXWaitX;

Для того чтобы созданное окно показалось, нужно вызвать функцию XMapRaised(можно использовать и XMapWindow, но эта функция более "универсальна", и может служить также и как "просьба" к оконному менеджеру переместить окно на передний план), и "подождать" всех событий X Window функцией glXWaitX перед переходом к следующему этапу(инициализации контекста OpenGL).

Создание контекста OpenGL

Завершающий этап прост до безобразия, но имеет свой нюанс. В X Window существует понятие прямого и непрямого рендеринга(direct/indirect rendering), "прямость/кривость" которого зависит от драйвера(или от того работает ли компьютер как терминал) :) Поэтому приходиться пробовать создать контекст с использованием direct rendering'а, и в случаи неудачи попытаться создать с использованием indirect:

  // пробуем Direct Render
  ogl_Context := glXCreateContext( scr_Display,
                                   ogl_VisualInfo,
                                   nil, TRUE );
  if not Assigned( ogl_Context ) Then
    begin
      // пробуем Indirect Render
      ogl_Context := glXCreateContext( scr_Display,
                                       ogl_VisualInfo,
                                       nil, FALSE );

      if not Assigned( ogl_Context ) Then
        begin
          // Ошибка
          exit;
        end;
    end;

  if not glXMakeCurrent( scr_Display, wnd_Handle, ogl_Context ) Then
    begin
      // Ошибка
      exit;
    end;

glXCreateContext и glXMakeCurrent являются полными аналогами wglCreateContext и wglMakeCurrent соответственно, поэтому их полное описание опущу. Хотя в функции glXCreateContext есть в некоторых случаях полезный параметр - shareList, он указывает на контекст с которым следует разделять ресурсы создаваемого контекста(текстуры и пр.). Если же этого не требуется, передается nil.

В заключение

Т.к. без обработки событий, окно не нарисуется, то приведу кусок кода главного цикла приложения:

  while XPending( scr_Display ) <> 0 do
    begin
      XNextEvent( scr_Display, @Event );
    end;

Описание дам в следующей части, а пока нужно еще упомянуть два момента. Первый это то как закончить рендер OpenGL и выполнить Swap буферов:

  glFlush;
  glXWaitGL;
  glXSwapBuffers( scr_Display, wnd_Handle );

И второй - как установить заголовок для окна(куда ж без "Hello, World!" в первом примере? :)):

  // Заполняем строку именем
  wnd_Caption := 'Hello, World!';
  // Заносим имя в специальную структуру
  XStringListToTextProperty( @wnd_Caption, 1, @wnd_Title );
  // Устанавливаем имя заголовка
  XSetWMName( scr_Display, wnd_Handle, @wnd_Title );

Ссылка на готовый пример приведена в конце статьи. Для компиляции использовался Free Pascal для Linux версии 2.2, но думаю работать будет и на более ранних версиях.

Замечания

Приведенный код по выбору формата пикселей является не очень корректным, и правильно было бы проверить все возможные значения размера z-буфера(32, 24, 16) и выбрать тот, который поддерживает видеокарта(также стоит поступить и с stencil'ом), т.к. казусы могут быть самые разные. Но для наглядности я опустил подобные проверки.

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


MIRGames
Valid HTML 4.0.1 Strict Valid CSS 3.0 Strict