Почему glReadPixels работает так медленно и есть ли какая-либо альтернатива?

#c #performance #opengl #screenshot #freeglut

#c #Производительность #opengl #скриншот #freeglut

Вопрос:

Мне нужно делать снимки в каждом кадре, и мне нужна очень высокая производительность (я использую freeGlut). Я понял, что это можно сделать так внутри glutIdleFunc(thisCallbackFunction)

 GLubyte *data = (GLubyte *)malloc(3 * m_screenWidth * m_screenHeight);
glReadPixels(0, 0, m_screenWidth, m_screenHeight, GL_RGB, GL_UNSIGNED_BYTE, data);
// and I can access pixel values like this: data[3*(x*512   y)   color] or whatever
  

Это действительно работает, но у меня с этим огромная проблема, это действительно медленно. Когда мое окно 512×512, оно работает не быстрее 90 кадров в секунду, когда отображается только куб, без этих двух строк оно работает со скоростью 6500 кадров в секунду! Если мы сравним его с графическим движком irrlicht, там я могу это сделать

 // irrlicht code
video::IImage *screenShot = driver->createScreenShot();
const uint8_t *data = (uint8_t*)screenShot->lock();
// I can access pixel values from data in a similar manner here
  

и окно 512×512 работает со скоростью 400 кадров в секунду даже при загруженной огромной сетке (карта Quake 3)! Примите во внимание, что я использую OpenGL в качестве драйвера внутри irrlicht. На мой неопытный взгляд кажется glReadPixels , что он копирует данные каждого пикселя из одного места в другое, а (uint8_t*)screenShot->lock() просто копирует указатель на уже существующий массив. Могу ли я сделать что-то подобное последнему, используя freeGlut? Я ожидаю, что это будет быстрее, чем irrlicht.

Обратите внимание, что irrlicht также использует OpenGL (ну, он также предлагает DirectX и другие опции, но в приведенном выше примере я использовал OpenGL, и, кстати, он был самым быстрым по сравнению с другими вариантами)

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

1. Найдите объекты буфера пикселей, которые позволяют вам делать это асинхронно.

2. Если вы поместите этот код в функцию ожидания, он может вызываться более одного раза за кадр. Кроме того, у вас происходит утечка памяти, если вы никогда не освобождаете выделяемые вами данные.

3. @RetoKoradi ну, нет, я не выполняю эти строки буквально каждый раз idle , когда вызывается. data выделяется только один раз в конструкторе, и я использую его снова и снова. Теперь я изучаю PBO….

Ответ №1:

Для управления конвейером рендеринга используются методы OpenGL. По своей природе, пока видеокарта показывает изображение зрителю, выполняются вычисления следующего кадра. Когда вы вызываете glReadPixels ; графическая карта ожидает завершения текущего кадра, считывает пиксели, а затем начинает вычислять следующий кадр. Поэтому конвейер останавливается и становится последовательным.

Если вы можете удерживать два буфера и указывать видеокарте считывать данные в эти буферы, меняя местами каждый кадр; тогда вы можете считывать данные из своего буфера с опозданием на 1 кадр, но без остановки конвейера. Это называется двойной буферизацией. Вы также можете выполнить тройную буферизацию с поздним чтением 2 кадров и так далее.

Существует относительно старая веб-страница, описывающая явление и реализацию здесь: http://www.songho.ca/opengl/gl_pbo.html

Также в Интернете есть много руководств по фреймбуферам и рендерингу в текстуру. Один из них здесь: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture /

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

1. Итак, я понял, как визуализировать текстуру, но я не знаю, как переместить эту текстуру в основной буфер, чтобы она отображалась на экране, и, самое главное, как получить доступ к значениям пикселей из этой текстуры с помощью индексов (как я показал в вопросе). Что мне делать?

2. Вы можете использовать glBlitFramebuffer , привязав задний буфер as DRAW_BUFFER и привязав свой фреймбуфер текстуры как READ_BUFFER априори. Это для вывода изображения на экран. Для чтения значений пикселей вы должны использовать объект буфера пикселей (он же PBO).

3. Мне удалось переместить изображение из объекта буфера кадров (где я визуализировал свою сцену) в основной буфер, чтобы оно отображалось на экране, но я все еще не могу получить доступ к значениям пикселей. Я не совсем понимаю, где должен появиться PBO. Этот учебник ( songho.ca/opengl/gl_pbo.html ) мне кажется, не так-то просто следовать, исходный код имеет длину 600 строк и выглядит действительно запутанным. Что мне делать? Задайте другой вопрос конкретно об этом? Я даже не знаю, что именно спросить. Любые советы будут оценены.

4. К сожалению, у меня нет рабочего примера кода. Основная идея songho.ca/opengl/gl_pbo.html вы привязываете один из 2 PBO GL_PIXEL_PACK_BUFFER , и ваш glReadPixels становится мгновенным на стороне клиента (ваш код), но вы можете читать из буфера только в том случае, если предыдущие операции с буфером выполнялись на стороне сервера (видеокарта). Поэтому вы можете прочитать предыдущий кадр почти мгновенно.

5. кстати, я использую интегрированный графический процессор (Intel HD graphics 4000), насколько я знаю, CPU и GPU используют одну и ту же память, так почему мне нужно загружать? Почему невозможно получить указатель?