Содержание   Далее >>

Использование OpenGL в Linux: GLX

Автор: Роман Подобедов
Email: romka@ut.ee
WWW: romka.demonews.com
Версия: 1.0 (29 Января 2001)

Содержание:

  1. Введение
  2. Прямое и косвенное воспроизведение
  3. Основы построения программы
  4. Внеэкранное воспроизведение
  5. Получение информации о GLX

 

Введение

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

Прямое и косвенное воспроизведение

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

GLX поддерживает два типа воспроизведения - прямое и косвенное. При косвенном воспроизведении поток OpenGL команд передается стандартным способом для X Windows и совмещается с потоком X команд, при этом не важно где находятся клиент и сервер, на одном компьютере или на разных (если на разных, то данные передаются через сеть). При прямом воспроизведении, данные идущие от процесса не кодируются и не сливаются с потоком X, они напрямую передаются в графический конвейер. Естественно, при таком способе увеличивается скорость воспроизведения. Одним важным и отличительным свойством прямого воспроизведения от косвенного, является то, что процесс должен иметь прямой доступ к графическому конвейеру и вследствие этого клиент и сервер должны находится на одной машине, т.е. подключение к X серверу должно быть локальным.

На рисунке показана схема прямого и косвенного воспроизведения:

 

Как видно из рисунка, при косвенном воспроизведении, данные кодируются (т.е. сливаются с потоком, идущим от других процессов к X серверу) и передаются в X сервер, где происходит их разделение: одна часть данных идет в X Renderer, другая в OpenGL Renderer, откуда потом во буфер фрейма. При прямом воспроизведении, данные напрямую поступают в OpenGL Renderer и оттуда в буфер кадра.

 

Основы построения программы

Для того, чтобы начать непосредственно работать с командами OpenGL, необходимо проделать предварительные настройки, связанные с оконной системой X Windows. Итак, рассмотрим основные этапы в построении примитивной программы (в дальнейшем это будет основной костяк, от которого можно будет строить более сложные программы):

  1. Соединение с X сервером
  2. Проверка того, поддерживает ли X сервер расширение GLX, если да, то переходим к шагу 3, если нет, то выходим из программы
  3. Нахождение подходящего формата пикселей, который удовлетворял бы желаниям клиента и возможностям сервера.
  4. Создание окна
  5. Создание OpenGL контекста и связывание его с поверхностью рисования (в данном случае с нашим окном)
  6. Дальнейшие шаги программы с последующим переходом в основной цикл событий X

Рассмотрим эти шаги более подробно.

Соединение с X сервером происходит за пределами OpenGL и GLX, делается это стандартными средствами X Windows.

Display *XOpenDisplay(char *display_name);

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

Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base);

Эта команда возвращает True, если расширение поддерживается и False , если не поддерживается. Параметр dpy определяет соединение с X сервером. Второй параметр используется для возвращения первого кода ошибки. Третий параметр не используется.

Следующим шагом, как уже было сказано, является нахождение подходящего формата пикселей или, говоря по другому - размер и свойства буферов, куда будет выводиться (отображаться) информация. Весь процесс происходит следующим образом. Клиент задает параметры формата пикселей (например глубину цвета, двойную буферизацию, ...), далее он передает эту информацию на сервер, который в свою очередь смотрит свои возможности и возвращает назад клиенту именно те параметры, с которыми сервер сможет работать. Если какие-то параметры клиента не совпадают с возможностями сервера, то сервер выбирает наиболее подходящие (например, если клиент запрашивает у сервера 32 битную глубину цвета, а поддерживается всего 16 битная глубина, то сервер возвратит клиенту именно 16 бит). Далее клиент в праве решать, что делать с данной информацией. Если ему не достаточно этих данных, т.е. сервер не удовлетворяет потребностям клиента, то он может выдать сообщение об ошибке или повторить запрос с другими параметрами (Типичный случай, когда программа рассчитана на использование Stencil или Stereo буферов, а сервер их не поддерживает). Но в большинстве случаев сервер удовлетворяет запросам клиентов.

Рассмотрим теперь функцию, которая позволяет найти подходящий формат пикселей:

XVisualInfo* glXChooseVisual(Display *dpy, int screen, int *attrib_list);

Как и в предыдущей команде, параметр dpy определяет соединение с X сервером. Номер экрана - параметр screen. С помощью третьего параметра клиент определяет те параметры, которые он хочет передать серверу на согласование. Параметр передается в виде указателя на массив, в котором указываются атрибуты и их значения (атрибуты не обязательны, если они не указаны, то им присваиваются значения по умолчанию). В следующих двух таблицах указываются всевозможные атрибуты, их назначение (Таблица 1), их значения по умолчанию и критерии выборки (Таблица 2):

Атрибут

Тип

Значение

GLX_USE_GL

boolean

True, если OpenGL воспроизведение поддерживается

GLX_BUFFER_SIZE

integer

Глубина буфера цвета

GLX_LEVEL

integer

Уровень буфера кадра

GLX_RGBA

boolean

True, если поддерживается RGBA режим

GLX_DOUBLEBUFFER

boolean

True, если поддерживается двойная буферизация

GLX_STEREO

boolean

True, если поддерживается стерео режим

GLX_AUX_BUFFERS

integer

Количество вспомогательных буферов

GLX_RED_SIZE

integer

Количество битов красного в буфере кадра

GLX_GREEN_SIZE

integer

Количество битов зеленого в буфере кадра

GLX_BLUE_SIZE

integer

Количество битов синего в буфере кадра

GLX_ALPHA_SIZE

integer

Количество битов альфа в буфере кадра

GLX_DEPTH_SIZE

integer

Количество битов в буфере глубины

GLX_STENCIL_SIZE

integer

Количество битов в буфере трафарета

GLX_ACCUM_RED_SIZE

integer

Количество битов красного в буфере аккумулятора

GLX_ACCUM_GREEN_SIZE

integer

Количество битов зеленого в буфере аккумулятора

GLX_ACCUM_BLUE_SIZE

integer

Количество битов синего в буфере аккумулятора

GLX_ACCUM_ALPHA_SIZE

integer

Количество битов альфа в буфере аккумулятора

Таблица 1

Атрибут

Значение по умолчанию

Критерии выборки

GLX_USE_GL

True

точно

GLX_BUFFER_SIZE

0

минимум, наименьшее

GLX_LEVEL

0

точно

GLX_RGBA

False

точно

GLX_DOUBLEBUFFER

False

точно

GLX_STEREO

False

точно

GLX_AUX_BUFFERS

0

минимум, наименьшее

GLX_RED_SIZE

0

минимум, наибольшее

GLX_GREEN_SIZE

0

минимум, наибольшее

GLX_BLUE_SIZE

0

минимум, наибольшее

GLX_ALPHA_SIZE

0

минимум, наибольшее

GLX_DEPTH_SIZE

0

минимум, наибольшее

GLX_STENCIL_SIZE

0

минимум, наименьшее

GLX_ACCUM_RED_SIZE

0

минимум, наибольшее

GLX_ACCUM_GREEN_SIZE

0

минимум, наибольшее

GLX_ACCUM_BLUE_SIZE

0

минимум, наибольшее

GLX_ACCUM_ALPHA_SIZE

0

минимум, наибольшее

Таблица 2

Необходимо пояснить такое понятие, как критерии выборки. Если критерий выборки 'точно', то значение атрибута должно указываться точно, т.е. значение этого атрибута выбирается таким, какое указано в списке параметров. Критерий выборки 'минимум, наибольшее' означает, что если значение атрибута равно нулю, то выбирается минимальное доступное значение, а если значение не равно нулю, то выбирается наибольшее из возможных значений, аналогично и с критерием 'минимум, наименьшее' - минимум при нуле, наименьшее при положительном значении атрибута. Рассмотрим на примере. Пусть задан такой массив атрибутов:

int Attributes[] = {
GLX_RGBA,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_DEPTH_SIZE, 12,
GLX_DOUBLEBUFFER,
None};

Видно, что у GLX_RGBA критерий выборки 'точно', поэтому для GLX_RGBA устанавливается значение True. Для GLX_RED_SIZE установлено значение 1, критерий выборки для GLX_RED_SIZE - 'минимум, наибольшее', поэтому для GLX_RED_SIZE выбирается максимальное возможное количество бит красного цвета, поддерживаемого системой. Аналогично с GLX_GREEN_SIZE, GLX_BLUE_SIZE и GLX_DEPTH_SIZE. Также установлено значение GLX_DOUBLEBUFFER, которое указывает на то, что должна использоваться двойная буферизация. Последние значение - None , оно должно быть в конце каждого списка, оно указывает системе на то, что больше атрибутов перечисляться не будет.

Создание окна происходит стандартными средствами X Windows:

Window XCreateWindow(
Display *display, // Соединение с X сервером
Window parent, // Родительское окно
int x,  int y, unsigned int width, unsigned int height, // Координаты x, y и ширина и высота окна
unsigned int border_width, // Ширина границы окна
int depth, // Глубина цвета окна
unsigned int class, // Класс окна
Visual *visual, // Тип поверхности
unsigned long valuemask, // Типы атрибутов окна
XSetWindowAttributes *attributes // Атрибуты окна
);

Для создания OpenGL контекста используется команда:

GLXContext glXCreateContext(Display *dpy, XVisualInfo* visual, GLXContext share_list, Bool direct);

Параметры dpy - соединение с X сервером, visual - указывает на структуру, возвращаемой командой glXChooseVisual , share_list - какой-то другой контекст, с которым вы хотите разделить пространство для дисплейных списков и текстур, обычно он равен нулю (Null). Параметр direct, определяет, какого типа будет контекст - прямо-воспроизводящим (direct = True), если реализация поддерживает прямое воспроизведение, либо косвенно воспроизводящим (direct= False).

Далее, необходимо сделать контекст текущим для поверхности рисования.

Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, GLXContext ctx);

Параметр dpy означает соединение с X сервером, drawable - поверхность рисования, ctx - OpenGL контекст.

Далее необходимо отобразить наше окно на экране с помощью функции:

XMapWindow(Display *display, Window w);

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

Следующий простой пример показывает то, как построить окно и настроить вывод OpenGL в него: glx_example1.cpp

 

Внеэкранное воспроизведение

Помимо прямого отображения графической информации в буфер кадра, существует еще один способ воспроизведения - внеэкранное. При таком типе воспроизведения информация отображается в пиксельные карты (Pixmap) или попросту говоря в область обычной памяти.

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

Для использования внеэкранного воспроизведения нужно проделать некоторые дополнительные шаги:

1. Создать пиксельную карту средствами X Windows
2. Создать пиксельную карту средствами GLX (т.е. совместимую с GLX)
3. При указании текущего OpenGL контекста, указать не на окно, а на пиксельную карту GLX

Теперь подробнее.

Стандартная пиксельная карта X Windows создается командой:

Pixmap XCreatePixmap(
Display *display, // Соединение с X сервером
Drawable d, // Поверхность
unsigned int width, unsigned int height, // Ширина и высота
unsigned int depth // Глубина
);

Создание пиксельной карты GLX и связывание ее со стандартной пиксельной картой X Windows производится командой:

GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo* visual, Pixmap pixmap);

Здесь dpy - соединение с X сервером, visual - информация о формате пикселей, pixmap - пиксельная карта X Windows.

Теперь, чтобы отобразить картинку на экран, надо скопировать область памяти пиксельной карты в область памяти нашего окна, например стандартной командой X Windows:

XCopyArea(
Display *display, // Соединение с X сервером
Drawable src,  Drawable dest, // Поверхность откуда копируем и поверхность куда копируем
GC gc, // Графический контекст
int src_x, int src_y, // Координаты исходной копируемой области
unsigned int width, unsigned int height, // Ширина и высота копируемой области
int dest_x, int dest_y // Координаты области, куда копируем исходную область
);

Пример использования внеэкранного воспроизведения: glx_example2.cpp

 

Получение информации о GLX

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

Bool glXQueryVersion(Display *dpy, int *major, int *minor);

Параметры: dpy - соединение с X сервером, major - старший номер версии, minor - младший номер версии.

Следующая команда позволяет определять атрибуты, перечисленные в таблице 1.

int glXGetConfig(Display *dpy, XVisualInfo *visual, int attribute, int *value);

Параметры: dpy - соединение с X сервером, visual - информация о формате пикселей, attribute - атрибут, значение которого необходимо получить (см. таблицу 1), value - возвращаемое значение указанного атрибута.

Следующие три команды доступны только начиная с версии 1.1. Первая из них позволяет получить GLX расширения, которые доступны в данной реализации.

const char *glXQueryExtensionsString(Display *dpy, int screen);

Две следующие команды определяют различные параметры как с клиентской стороны, так и с серверной. Параметр name указывает, что именно необходимо получить и может быть одной из следующих констант: GLX_VENDOR, GLX_VERSION или GLX_EXTENSIONS (что соответственно значит: производитель, версия и расширения GLX)

const char *glXGetClientString(Display *dpy, int name);

const char* glXQueryServerString(Display *dpy, int screen, int name);

Параметр dpy во всех командах означает соединение с X сервером. Параметр screen - номер экрана.

Пример получения информации о GLX: glx_example3.cpp