19 дек. 2010 г.
Про страхование
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 контекста выглядит так:
- создаем окно;
- с помощью wglCreateContext создаем в нем хоть какой-нибудь rendering context опенгла (тут есть неясный мне момент: надо ли устанавливать перед этим DC окна PixelFormat, я устанавливал, может быть это и не нужно было) и выбираем его (wglMakeCurrent);
- загружаем нужные адреса функций. Потребуются как минимум wglChoosePixelFormatARB и wglCreateContextAttribsARB. В случае использования GLee просто вызваем GLeeInit() - она загрузит все;
- убиваем контекст опенгла (wglMakeCurrent(0, 0); wglDeleteContext(hRC);), убиваем следом и само окно: вроде как повторно использовать его не получится, я, опять же, не проверял;
- создаем новое окно;
- создаем и устанавливаем этому окну pixel format с помощью функций wglChoosePixelFormatARB и SetPixelFormat (в принципе, можно обойтись и без wglChoosePixelFormatARB, но тогда настроить можно будет только то, что есть в структуре PIXELFORMATDESCRIPTOR, передаваемой в SetPixelFormat): полученный из wglChoosePixelFormatARB pixelFormat передается как второй аргумент SetPixelFormat (либо пишем туда 0, если не заморачивались);
- создаем rendering context функцией wglCreateContextAttribsARB;
- выбираем его (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 не обойтись (нвидиевские драйвера, видимо, прощают гораздо больше, чем атишные), и обложил все вызовы опенгла проверками того, что они сработали — дело поиска косяков пошло веселее, но возвращаемые ошибки иногда помогали слабо. Так что надо проверять на разном железе — необычное занятие для человека, занимающегося программированием для консолей.