Все начинается с glReadPixels
, который вы будете использовать для передачи пикселей, хранящихся в конкретный буфер на GPU в основную память (RAM). Как вы заметите в документации, нет аргументов для выбора буфера. Как обычно с OpenGL, текущий буфер для чтения - это состояние, которое вы можете установить с помощью glReadBuffer
.
Итак, очень простой метод закадрового рендеринга будет примерно таким. Я использую псевдокод C ++, поэтому он, скорее всего, будет содержать ошибки, но должен прояснить общий поток:
//Before swapping
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_BACK);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);
Это будет читать текущий задний буфер (обычно буфер, в который вы рисуете). Вы должны вызвать это перед заменой буферов. Обратите внимание, что вы также можете отлично прочитать задний буфер с помощью вышеуказанного метода, очистить его и нарисовать что-то совершенно другое, прежде чем заменять его местами. Технически вы также можете читать передний буфер, но это часто не рекомендуется, так как теоретически реализациям разрешено делать некоторые оптимизации, которые могут сделать ваш передний буфер мусором.
У этого есть несколько недостатков. Во-первых, мы действительно не делаем закадровый рендеринг. Рендерим в экранные буферы и читаем из них. Мы можем эмулировать закадровый рендеринг, никогда не меняя задний буфер местами, но это кажется неправильным. Кроме того, передний и задний буферы оптимизированы для отображения пикселей, а не для их чтения. Вот где в игру вступают объекты Framebuffer.
По сути, FBO позволяет вам создавать нестандартный буфер кадра (например, буферы FRONT и BACK), которые позволяют рисовать в буфер памяти вместо экранных буферов. На практике вы можете рисовать либо в текстуре, либо в буфере рендеринга. Первый оптимален, когда вы хотите повторно использовать пиксели в самом OpenGL в качестве текстуры (например, наивная «камера безопасности» в игре), второй - если вы просто хотите визуализировать / считывать. При этом приведенный выше код стал бы чем-то вроде этого, снова псевдокодом, так что не убивайте меня, если я ошибся или забыл некоторые инструкции.
//Somewhere at initialization
GLuint fbo, render_buf;
glGenFramebuffers(1,&fbo);
glGenRenderbuffers(1,&render_buf);
glBindRenderbuffer(render_buf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8, width, height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf);
//At deinit:
glDeleteFramebuffers(1,&fbo);
glDeleteRenderbuffers(1,&render_buf);
//Before drawing
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo);
//after drawing
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);
// Return to onscreen rendering:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
Это простой пример, на самом деле вам, вероятно, также понадобится хранилище для буфера глубины (и трафарета). Вы также можете выполнить рендеринг в текстуру, но я оставлю это в качестве упражнения. В любом случае теперь вы будете выполнять реальный закадровый рендеринг, и он может работать быстрее, чем чтение обратного буфера.
Наконец, вы можете использовать объекты пиксельного буфера, чтобы сделать считанные пиксели асинхронными. Проблема в том, что glReadPixels
блокируется до тех пор, пока данные пикселей не будут полностью переданы, что может привести к остановке вашего процессора. С PBO реализация может немедленно вернуться, поскольку она все равно контролирует буфер. Только при отображении буфера конвейер будет блокировать. Однако PBO можно оптимизировать для буферизации данных исключительно в ОЗУ, поэтому этот блок может занять намного меньше времени. Код считываемых пикселей будет примерно таким:
//Init:
GLuint pbo;
glGenBuffers(1,&pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, width*height*4, NULL, GL_DYNAMIC_READ);
//Deinit:
glDeleteBuffers(1,&pbo);
//Reading:
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,0); // 0 instead of a pointer, it is now an offset in the buffer.
//DO SOME OTHER STUFF (otherwise this is a waste of your time)
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); //Might not be necessary...
pixel_data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
Деталь в кепках обязательна. Если вы просто отправите glReadPixels
в PBO, а затем glMapBuffer
этого PBO, вы не получите ничего, кроме большого количества кода. Конечно, glReadPixels
может вернуться немедленно, но теперь glMapBuffer
остановится, потому что он должен безопасно отобразить данные из буфера чтения в PBO и в блок памяти в основной RAM.
Также обратите внимание, что я использую GL_BGRA повсюду, потому что многие видеокарты внутренне используют его как оптимальный формат рендеринга (или версию GL_BGR без альфа). Это должен быть самый быстрый формат для такой передачи пикселей. Я попытаюсь найти статью nvidia, которую прочитал об этом несколько месяцев назад.
При использовании OpenGL ES 2.0 GL_DRAW_FRAMEBUFFER
может быть недоступен, вы должны просто использовать GL_FRAMEBUFFER
в этом случае.
person
KillianDS
schedule
28.08.2012