4 дек. 2010 г.

Про опенгл 3

Пост является вольным пересказом Getting started и Creating an OpenGL Context с OpenGL wiki с упоминанием граблей, на которые я наступил.


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


Надо наверстывать упущенное, и разобраться хотя бы с третьим опенглом, хотя бы потому, что OpenGL extension viewer говорит, что моя видеокарта не поддерживает даже OpenGL 4.0.


В третьем опенгле Khronos group решили выкинуть нафиг Fixed function pipeline. В спецификации 3.0 функции, относящиеся к FFP были помечены как deprecated, в 3.1 они были убраны совсем.


Кроме того, микрософту с его DirectX на опенгл плевать и .h файлы поставляемые с Windows SDK были заморожены где-то в районе спецификации 1.1. Что означает, что функции, появившиеся в более поздних версиях там не объявлены. Что в свою очередь означает, что адреса нужных функций придется грузить ручками (с помощью wglGetProcAddress), а прототипы набивать где-то у себя в исходниках.


Те, кто не любит заниматься такой фигней могут воспользоваться одной из библиотек, облегчающей данный процесс. Я после того, как проделал это с десятком функций, решил, что рутинное повторение операции "объявить указатель на функцию/загрузить адрес" мне особо понимания происходящего не прибиавит и взял GLee. Использование ее заключается в том, что из zip-файла берутся .h и .c файлы и добавляются в проект (я использую Visual Studio), если проект C++ и используются precompiled headers, надо .c файл переименовать в .cpp, иначе студия будет ругаться, что нельзя использовать precompiled headers в проекте на двух языках.


Вообще, пользование опенглом в Windows начинается с того, что создается окно, в нем создается контекст OpenGL и после этого можно рисовать. Почти. Порядок создания контекста спецификацией не оговаривается и оставляется на совести разработчика реализации опенгла на каждой конкретной платформе. Соответственно, функция wglCreateContext, имеющаяся в распоряжении человека, заинклюдившего gl.h из Windows SDK, создает контекст опенгла, совместимый со спецификацией 1.1.


Контекст, совместимый с 3.x создается функцией wglCreateContextAttribsARB (да, все, что имеет префикс wgl, является Windows-specific, так что неумеренное использование приведет к проблемам с переносимостью на другие ОСи), адрес которой надо грузить вручную. Но загрузить адрес опенгловой функции можно только уже имея проинициализированный контекст опенгла, иначе wglGetProcAddress вас пошлет далеко и надолго. Соответственно, процесс создания OpenGL 3.x контекста выглядит так:



  1. создаем окно;

  2. с помощью wglCreateContext создаем в нем хоть какой-нибудь rendering context опенгла (тут есть неясный мне момент: надо ли устанавливать перед этим DC окна PixelFormat, я устанавливал, может быть это и не нужно было) и выбираем его (wglMakeCurrent);

  3. загружаем нужные адреса функций. Потребуются как минимум wglChoosePixelFormatARB и wglCreateContextAttribsARB. В случае использования GLee просто вызваем GLeeInit() - она загрузит все;

  4. убиваем контекст опенгла (wglMakeCurrent(0, 0); wglDeleteContext(hRC);), убиваем следом и само окно: вроде как повторно использовать его не получится, я, опять же, не проверял;

  5. создаем новое окно;

  6. создаем и устанавливаем этому окну pixel format с помощью функций wglChoosePixelFormatARB и SetPixelFormat (в принципе, можно обойтись и без wglChoosePixelFormatARB, но тогда настроить можно будет только то, что есть в структуре PIXELFORMATDESCRIPTOR, передаваемой в SetPixelFormat): полученный из wglChoosePixelFormatARB pixelFormat передается как второй аргумент SetPixelFormat (либо пишем туда 0, если не заморачивались);

  7. создаем rendering context функцией wglCreateContextAttribsARB;

  8. выбираем его (wglMakeCurrent);


Мой код выглядит примерно так:


m_window = createNewWindowForOpenGL(hInstance); // моя функция создания окна
m_dc = GetDC(m_window);

const int ATTR_LIST[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
// WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
// WGL_SAMPLES_ARB, 4,
0,
};

int pixelFormat;
UINT numFormats;

wglChoosePixelFormatARB(m_dc, ATTR_LIST, NULL, 1, &pixelFormat, &numFormats);

PIXELFORMATDESCRIPTOR pfd;
fillPFD(pfd); // моя функция заполнения PFD

SetPixelFormat(m_dc, pixelFormat, &pfd);

const int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
// WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0
};

m_rc = wglCreateContextAttribsARB(m_dc, NULL, attribs);

wglMakeCurrent(m_dc, m_rc);

int openGLMajorVersion, openGLMinorVersion;
glGetIntegerv(GL_MAJOR_VERSION, &openGLMajorVersion);
glGetIntegerv(GL_MINOR_VERSION, &openGLMinorVersion);

обработка ошибок для краткости выкинута. В переменных openGLMajorVersion и openGLMinorVersion находится реальная версия опенгла, с которой был создан контекст. Она может отличаться от запрошенной.


В созданном контексте хоть что-то вывести можно только с помощью Vertex buffer object'ов и Vertex array object'ов. Причем, в контексте 3.0 можно обойтись только VBO (там есть "умолчальный array object" с номером 0), а в более поздних версиях обязательно использование и VAO, а если включен forward compatible bit (последняя закомментированная строчка в коде), то и в 3.0 контексте обязательно использование VAO


Теперь о граблях. Изначально код я писал на компе с видеокартой NVidia и к моему удивлению написанный код почти сразу начал делать то, что я хотел. Позже выяснилось, что при попытке запустить ее на видеокарте ATI/AMD программа не работала: я видел только clear color и больше ничего. Ввиду моего некоторого недопонимания как делать правильно, а также недостатка проверок результатов вызовов функций, был (возможно зря) переписан код создания окон (изначально создавалось два окна сразу, в одном инициализировался стандартный контекст, получились указатели, окно убивалось и инициализировался нужный контекст во втором окне, после переписывания окна создавались и убивались строго последовательно). В конце концов, я выяснил, что использование VAO обязательно и просто одними VBO не обойтись (нвидиевские драйвера, видимо, прощают гораздо больше, чем атишные), и обложил все вызовы опенгла проверками того, что они сработали — дело поиска косяков пошло веселее, но возвращаемые ошибки иногда помогали слабо. Так что надо проверять на разном железе — необычное занятие для человека, занимающегося программированием для консолей.

Комментариев нет: