суббота, 30 ноября 2013 г.

Lazarus World. Используем GLX расширение для связи OpenGL с X Window System


Для создания окна будем использовать библиотеку xlib. Запустите Lazarus и создайте новый проект, для этого выберите пункт меню File>New>Program и нажмите кнопку OK (Смотрите рисунок 1). Назовите его ogl_p3.lpr и сохраните проект в домашней директории пользователя /home/[имя пользователя]/Projects/lazarus/ogl_p3/.

Рисунок 1. Создание нового проекта

Чтобы сохранить проект под выбранным именем, нажмите пункт меню File>Save. В качестве пути сохранения проекта укажите каталог который вы создали ранее.

Если посмотреть в редактор кода среды Lazarus, то можно увидеть уже введенный стартовый код проекта. Удалим лишнее и оставим только то, что нам пригодится в дальнейшем:

program ogl_p3;
uses
begin
end.

Теперь в секции uses подключим необходимые модули:

1) для работы с функциями связывающие opengl с x window system (glx)
2) для работы с функциями создания окна x window system (x, xutil, xlib)
3) для работы с функциями OpenGL (gl, glu)

Таким образом секция uses у нас примит вид:

uses glx, x, xutil, xlib, gl, glu;

Выше секции uses укажите режим синтаксиса совместимый с delphi

{$MODE delphi}

После секции uses создадим секцию глобальных переменных var, в данной секции определим следующие переменные:

var
  dpy: PDisplay;
  visinfo: PXVisualInfo;
  Attr: Array[0..10] of integer =
   (GLX_DEPTH_SIZE, 16,
    GLX_RGBA,
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_DOUBLEBUFFER,
    none);
  cm: TColormap;
  winAttr: TXSetWindowAttributes;
  win :TWindow;
  glXCont: GLXContext;

1) Указатель на структуру Display с именем dpy, которая определена в xlib и содержит информацию о соединении с X-сервером.
2) Указатель на структуру XvisualInfo c именем visinfo, которая определена в xlib и содержит информацию о визуальных характеристиках поддерживаемых экраном X-сервера.
3) Attr, массив атрибутов визуальных характеристик, которые мы запрашиваем у X-сервера.
4) cm хранит информацию о новой цветовой палитре окна, тип Tcolormap.
5) Структура TXSetWindowAttributes с именем winAttr, хранит все необходимые атрибуты для создания нового окна.
6) Идентификатор, с именем win, созданного окна типа Twindow.
7) Переменная хранящая информацию о созданном контексте рендеринга OpenGL.

Для начала нам нужно определится, что мы будем рисовать с помощью функций OpenGL. В книге Эдварда Эйнджела «Интерактивная компьютерная графика. Вводный курс на базе OpenGL» 2-е издание 2001г. написан си код алгоритма построения треугольника Серпинского. Так что попробуем его нарисовать.

Далее определим новый тип:

type
  point2 = array[1..2] of GLfloat;

Этот новый тип point2, мы будем использовать для описания координат одной точки.

Далее определи секцию var, где определим одну точку (начальная точка, откуда идут все расчеты) и массив из трех точек (вершины большого треугольника):

var
  p: point2 = (0.0, 0.0);
  vertices: array[1..3] of point2 = 
     ((0.0, 0.0), (250.0, 500.0), (500.0, 0.0));

Ниже напишем код процедуры перерисовки окна:

procedure redraw();
var
  j,k: integer;
begin
  Randomize;

  glClear(GL_COLOR_BUFFER_BIT);

  for k:=1 to 250000 do
  begin
    j := Random(3) + 1;

    p[1] := (p[1] + vertices[j][1]) / 2;

    p[2] := (p[2] + vertices[j][2]) / 2;

    glBegin(GL_POINTS);
      glVertex2fv(@p);
    glEnd;
end;

    glXSwapBuffers(dpy, win);
end;

В данной процедуре размещен кусок кода который рисует 250000 точек в определенных координатах согласно алгоритму, эти точки не выходя за пределы большого треугольника.
Здесь процедура glXSwapBuffers переключает сформировавшуюся картинку из буфера кадра на экран.
Ниже напишем процедуру инициализации обзора сцены:


procedure initgl();
begin
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glColor3f(1.0, 0.0, 0.0);

  glMatrixMode(GL_PROJECTION);

  glLoadIdentity();

  gluOrtho2D(0.0, 500.0, 0.0, 500.0);

  glMatrixMode(GL_MODELVIEW);

end;

Ниже напишем процедуру, которая вызывается каждый раз при изменение размеров окна:

procedure resize(width, height: integer);
begin
  glViewport(0, 0, width, height);
end;

Ниже напишем процедуру, в которой находится бесконечный цикл обработки событий окна:

procedure loop();
var
  event: TXEvent;
begin
  while true do
  begin
   XNextEvent(dpy, @event);
   case event._type of
    Expose: redraw();
    ConfigureNotify: resize(event.xconfigure.width,
      event.xconfigure.height);
    KeyPress: halt(1);
   end;
  end;
end;

в данном случае обрабатываются событие перерисовки окна (Expose), событие изменения размеров окна (ConfigureNotify), события нажатия клавиши (KeyPress).

Ниже определим секцию var, в которой определены переменные необходимые для последующего кода проекта в главных операторных скобках (begin ... end.)

var
  errorBase, eventBase: integer;
  title: String;
  window_title_property: TXTextProperty;

1) Переменные с именами errorBase и eventBase нужны для хранения кода ошибок возвращаемые процедурой glXQueryExtension.
2) Переменная строка с именем title для хранения названия заголовка окна
3) Переменная window_title_property необходима для установки имени приложения

Ниже напишем код главных операторных скобок:

begin
  initGlx();

  dpy := XOpenDisplay( nil );

  if (dpy = nil) then
  writeLn('Error: Could not connect to X server');

  if not (glXQueryExtension(dpy, errorBase, eventBase)) then
  writeLn('Error: GLX extension not supported');

  visinfo := glXChooseVisual(dpy, DefaultScreen(dpy), Attr);
  if (visinfo = nil) then
  writeLn('Error: Could not find visual');

  cm := XCreateColormap(dpy, RootWindow(dpy, visinfo.screen),
  visinfo.visual, AllocNone);

  winAttr.colormap := cm;
  winAttr.border_pixel := 0;
  winAttr.background_pixel := 0;
  winAttr.event_mask := ExposureMask or
      ButtonPressMask or
      StructureNotifyMask or
      KeyPressMask;

  win := XCreateWindow(dpy, RootWindow(dpy, visinfo.screen),
  0, 0, 500, 500, 0, visinfo.depth, InputOutput, visinfo.visual,
  CWBorderPixel or CWColormap or CWEventMask, @winAttr);

  title := 'ogl_p3';
  XStringListToTextProperty(@title, 1, @window_title_property);
  XSetWMName(dpy, win, @window_title_property);

  glXCont := glXCreateContext(dpy, visinfo, none, true);
  if (glXCont = nil) then
  writeLn('Error: Could not create an OpenGL rendering context');

  glXMakeCurrent(dpy, win, glXCont);

  XMapWindow(dpy, win);

  initgl();

  loop();

end.


1) Функция initGlx производит инициализацию расширения GLX.
2) Функция XopenDisplay устанавливает соединение клиента с X-сервером.
3) Функция glXQueryExtension запрашивает у X-сервера поддерживает ли он GLX расширения.
4) Функция glXChooseVisual запрашивает у X-сервера возможные визуальные характеристики экрана.
5) Функция XcreateColormap создает новую цветовую палитру для нового окна
6) Далее мы задаем необходимые атрибуты создаваемого окна:

winAttr.colormap := cm;
winAttr.border_pixel := 0;
winAttr.background_pixel := 0;
winAttr.event_mask := ExposureMask or
   ButtonPressMask or
   StructureNotifyMask or
   KeyPressMask;

7) Функция XcreateWindow создает новое окно согласно заданным атрибутам и полученным характеристикам.
8) Следующими строчками кода мы устанавливаем заголовок окна:


 title := 'ogl_p3';
 XStringListToTextProperty(@title, 1, @window_title_property);
 XSetWMName(dpy, win, @window_title_property);

9) Функция glXCreateContext создает контекст OpenGL
10) Функция glXMakeCurrent привязывает созданный контекст OpenGL к созданному окну.
11) Функция XmapWindow делает окно видимым
12) Перед тем как передать управление обработчику событий окна, необходимо выполнить процедуру initgl инициализации обзора сцены.
13) Запускаем бесконечный цикл обработки событий окна.

Теперь давайте соберем проект. Результат работы программы представлен рисунком 2.
  
Рисунок 2. Треугольник Серпинского

Код полностью:
program ogl_p3;

{$MODE delphi}

uses glx, x, xutil, xlib, gl, glu;

var
  dpy: PDisplay;
  visinfo: PXVisualInfo;
  Attr: Array[0..10] of integer =
    (GLX_DEPTH_SIZE, 16,
     GLX_RGBA,
     GLX_RED_SIZE, 1,
     GLX_GREEN_SIZE, 1,
     GLX_BLUE_SIZE, 1,
     GLX_DOUBLEBUFFER,
     none);
  cm: TColormap;
  winAttr: TXSetWindowAttributes;
  win :TWindow;
  glXCont: GLXContext;

type
   point2 = array[1..2] of GLfloat;
var
   p: point2 = (0.0, 0.0);
   vertices: array[1..3] of point2 =  
           ((0.0, 0.0), (250.0, 500.0), (500.0, 0.0));

procedure redraw();
var
   j,k: integer;
begin
  Randomize;

  glClear(GL_COLOR_BUFFER_BIT);

  for k:=1 to 250000 do
  begin
       j := Random(3) + 1;

       p[1] := (p[1] + vertices[j][1]) / 2;

       p[2] := (p[2] + vertices[j][2]) / 2;

       glBegin(GL_POINTS);
        glVertex2fv(@p);
       glEnd;
  end;

  glXSwapBuffers(dpy, win);
end;

procedure initgl();
begin
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glColor3f(1.0, 0.0, 0.0);

  glMatrixMode(GL_PROJECTION);

  glLoadIdentity();

  gluOrtho2D(0.0, 500.0, 0.0, 500.0);

  glMatrixMode(GL_MODELVIEW);

end;

procedure resize(width, height: integer);
begin
  glViewport(0, 0, width, height);
end;

procedure loop();
var
  event: TXEvent;
begin
  while true do
  begin
    XNextEvent(dpy, @event);
    case event._type of
         Expose: redraw();
         ConfigureNotify: resize(event.xconfigure.width,
                          event.xconfigure.height);
         KeyPress: halt(1);
    end;
  end;
end;

var
  errorBase, eventBase: integer;
  title: String;
  window_title_property: TXTextProperty;
begin
  initGlx();

  dpy := XOpenDisplay( nil );

  if (dpy = nil) then
  writeLn('Error: Could not connect to X server');

  if not (glXQueryExtension(dpy, errorBase, eventBase)) then
  writeLn('Error: GLX extension not supported');

  visinfo := glXChooseVisual(dpy, DefaultScreen(dpy), Attr);
  if (visinfo = nil) then
  writeLn('Error: Could not find visual');

  cm := XCreateColormap(dpy, RootWindow(dpy, visinfo.screen),
  visinfo.visual, AllocNone);

     winAttr.colormap := cm;
     winAttr.border_pixel := 0;
     winAttr.background_pixel := 0;
     winAttr.event_mask := ExposureMask or
                           ButtonPressMask or
                           StructureNotifyMask or
                           KeyPressMask;

  win := XCreateWindow(dpy, RootWindow(dpy, visinfo.screen),
  0, 0, 500, 500, 0, visinfo.depth, InputOutput, visinfo.visual,
  CWBorderPixel or CWColormap or CWEventMask, @winAttr);

  title := 'ogl_p3';
  XStringListToTextProperty(@title, 1, @window_title_property);
  XSetWMName(dpy, win, @window_title_property);

  glXCont := glXCreateContext(dpy, visinfo, none, true);
  if (glXCont = nil) then
  writeLn('Error: Could not create an OpenGL rendering context');

  glXMakeCurrent(dpy, win, glXCont);

  XMapWindow(dpy, win);

  initgl();

  loop();

end.


syntax highlighted by Code2HTML, v. 0.9.1