Можно ли использовать glReadPixels для чтения слоев из GL_TEXTURE_3D? - PullRequest
2 голосов
/ 16 февраля 2020

Я пытаюсь прочитать 3D-текстуру, которую я рендерил, используя FBO. Эта текстура настолько велика, что glGetTexImage приводит к ошибке GL_OUT_OF_MEMORY из-за того, что драйвер nvidia не выделяет память для промежуточного хранилища * (необходимо, я думаю, чтобы избежать изменения буфера назначения в случае ошибка).

Поэтому я подумал о том, чтобы получить эту текстуру слой за слоем, используя glReadPixels после рендеринга каждого слоя. Но glReadPixels не имеет индекса слоя в качестве параметра. Единственное место, где оно фактически выглядит как нечто, направляющее ввод / вывод к определенному слою, - это gl_Layer вывод в геометрический шейдер. И это для стадии написания, а не для чтения.

Поскольку я пытался просто делать вызовы к glReadPixels в любом случае после того, как я рендерил каждый слой, я получил только тексели для слоя 0. Так что, по крайней мере, glReadPixels не может не получить что-то .

Но вопрос в том, могу ли я получить произвольный слой 3D-текстуры, используя glReadPixels? И если нет, что я должен использовать вместо этого, учитывая вышеописанные ограничения памяти? Нужно ли сэмплировать слой из 3D-текстуры в шейдере, чтобы отобразить результат в 2D-текстуру, а затем прочитать эту 2D-текстуру ?


* Это не предположение, я фактически отследил его до неудачного вызова malloc (с размером текстуры в качестве аргумента) из общей библиотеки драйвера nvidia.

Ответы [ 3 ]

3 голосов
/ 16 февраля 2020

Если у вас есть доступ к GL 4.5 или ARB_get_texture_sub_image, вы можете использовать glGetTextureSubImage. Как следует из названия функции, он предназначен для запроса подраздела данных изображения текстуры. Это позволяет вам читать фрагменты текстуры без необходимости получать все целиком за один go.

Расширение кажется довольно широко поддерживаемым , доступным в любой реализации, которая все еще поддерживается его IHV.

1 голос
/ 16 февраля 2020

Да, glReadPixels может читать другие фрагменты из 3D-текстуры. Нужно просто использовать glFramebufferTextureLayer, чтобы прикрепить правильный текущий срез к FBO - вместо того, чтобы прикреплять полную трехмерную текстуру в качестве цветовой привязки. Вот код замены для glGetTexImage (специальный FBO для этого, fboForTextureSaving, должен быть сгенерирован заранее):

GLint origReadFramebuffer=0, origDrawFramebuffer=0;
gl.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &origReadFramebuffer);
gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &origDrawFramebuffer);
gl.glBindFramebuffer(GL_FRAMEBUFFER, fboForTextureSaving);
for(int layer=0; layer<depth; ++layer)
{
    gl.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                 texture, 0, layer);
    checkFramebufferStatus("framebuffer for saving textures");
    gl.glReadPixels(0,0,w,h,GL_RGBA,GL_FLOAT, subpixels+layer*w*h*4);
}
gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, origReadFramebuffer);
gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, origDrawFramebuffer);

В любом случае, это не является долгосрочным решением проблемы , Первой причиной ошибок GL_OUT_OF_MEMORY с большими текстурами на самом деле является нехватка ОЗУ или VRAM. Это тоньше: каждая текстура, размещенная на GPU, отображается в адресное пространство процесса (по крайней мере, в Linux / nvidia). Таким образом, если ваш процесс не malloc хотя бы половину доступной ему оперативной памяти, его адресное пространство может уже использоваться этими большими отображениями. Добавьте к этому немного фрагментации памяти, и вы получите либо GL_OUT_OF_MEMORY, либо malloc сбой, либо std::bad_alloc где-то даже раньше, чем ожидалось.

Надлежащее долгосрочное решение - принять 64 и скомпилируйте ваше приложение как 64-битный код. Это то, что я в итоге сделал, избавившись от всего этого послойного кладжа и немного упростив код.

0 голосов
/ 16 февраля 2020

Итак, получив 3D-текстуру, вы можете сделать это:

for (z=0;z<z_resolution_of_your_txr;z++)
 {
 render_textured_quad(using z slice of 3D texture);
 glReadPixels(...);
 }

Лучше всего соответствовать размеру QUAD вашей 3D-текстуры x, y разрешений и использовать GL_NEAREST фильтрацию ...

Это будет медленно, поэтому, если вы не работаете в Intel и хотите работать быстрее, вы можете вместо этого использовать рендеринг для 2D-текстуры и использовать glGetTexImage на целевой 2D-текстуре вместо glReadPixels.

Здесь приведены примеры шейдеров для рендеринга среза z:

Вершина:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
uniform float aspect;
layout(location=0) in vec2 pos;

out smooth vec2 vpos;
//------------------------------------------------------------------
void main(void)
    {
    vpos=pos;
    gl_Position=vec4(pos.x,pos.y*aspect,0.0,1.0);
    }
//------------------------------------------------------------------

Фрагмент:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
uniform float slice=0.25;       // <0,1> slice of txr
in smooth vec2      vpos;
uniform sampler3D   vol_txr;    // 3D texture unit used
out layout(location=0) vec4 frag_col;
void main()
    {
    frag_col=texture(vol_txr,vec3(0.5*(vpos+1.0),slice));
    }
//---------------------------------------------------------------------------

Таким образом, вам нужно менять форму среза перед каждым срезом. Сам рендеринг - это всего лишь один QUAD, покрывающий экран <-1, + 1>, в то время как область просмотра соответствует текстуре с разрешением x, y ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...