{{notification.text}}

MirGames

Не буду петь хвалебные оды такой прекрасной ОС как 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'ом), т.к. казусы могут быть самые разные. Но для наглядности я опустил подобные проверки.

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

Пример к статье (2 kb)
09.04.08 05:15