Использование OpenGL в Linux: GLX
Автор: Роман Подобедов
Email: romka@ut.ee
WWW: romka.demonews.com
Версия: 1.0
(29 Января 2001)
Содержание:
Как и во всех других операционных системах, в 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. Итак, рассмотрим основные этапы в построении примитивной программы (в дальнейшем это будет основной костяк, от которого можно будет строить более сложные программы):
Рассмотрим эти шаги более подробно.
Соединение с 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:
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