PDA

View Full Version : Layered Cube Map Rendering - only one face written



Marco Di Benedetto
03-01-2012, 05:00 AM
Hi,

I have problems with layered framebuffers and cube maps.
After succesfully attaching a cube map texture to a framebuffer with glFramebufferTexture and performing a rendering, it turns out that only the first face is actually written.
On the other hand, layered rendering works as expected with a texture 2D array with 6 layers.
I tried with several formats, always obtaining a complete framebuffer but also with the same issue.
I prepared two small test cases that use 1x1 cube map and 2D array. They initialize the face/layers with some values, attach them, execute just a clear to black, an then read back the result.
As you will see, the only difference between the two tests is just the attached resource. 2D array gives the expected results (black texels in all layers), while cube map fails in all faces but X+.

Here is the code (you can copy & paste it in whichever GL application after glew initialization):



void checkGLError(const char * msg)
{
const GLenum err = glGetError();
if (err == GL_NO_ERROR) return;
printf("%s: GL ERROR %d\n", msg, int(err));
}

void testLayeredCubeMap(void)
{
glGetError(); // flush error

const GLubyte colors[6][4] =
{
{ 255, 0, 0, 255 }, // red
{ 0, 255, 0, 255 }, // green
{ 0, 0, 255, 255 }, // blue
{ 255, 255, 0, 255 }, // yellow
{ 0, 255, 255, 255 }, // cyan
{ 255, 0, 255, 255 } // magenta
};

// create cubemap texture
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
for (int face=0; face<6; ++face)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors[face]);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
checkGLError("CubeMap - Texture Creation");

// create layered framebuffer
GLuint fbo = 0;
glGenFramebuffers(1, &amp;fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
checkGLError("CubeMap - Framebuffer Creation");
if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
printf("CubeMap - Framebuffer Not Complete.\n");
return;
}

// clear
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
checkGLError("CubeMap - Framebuffer Clear");

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glGetError(); // flush error

// readback and test
GLubyte texel[4];
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
for (int face=0; face<6; ++face)
{
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
printf("CubeMap Face %d: [%.3d, %.3d, %.3d, %.3d]\n", face, int(texel[0]), int(texel[1]), int(texel[2]), int(texel[3]));
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
checkGLError("CubeMap - Texture Readback");

// clear resources
glDeleteFramebuffers(1, &amp;fbo);
glDeleteTextures(1, &amp;tex);
checkGLError("CubeMap - Resources Destruction");
}

void testLayered2DArray(void)
{
glGetError(); // flush error

const GLubyte colors[6][4] =
{
{ 255, 0, 0, 255 }, // red
{ 0, 255, 0, 255 }, // green
{ 0, 0, 255, 255 }, // blue
{ 255, 255, 0, 255 }, // yellow
{ 0, 255, 255, 255 }, // cyan
{ 255, 0, 255, 255 } // magenta
};

// create 2D array texture
GLuint tex = 0;
glGenTextures(1, &amp;tex);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
checkGLError("2D Array - Texture Creation");

// create layered framebuffer
GLuint fbo = 0;
glGenFramebuffers(1, &amp;fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
checkGLError("2D Array - Framebuffer Creation");
if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
printf("2D Array - Framebuffer Not Complete.\n");
return;
}

// clear
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
checkGLError("2D Array - Framebuffer Clear");

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glGetError(); // flush error

// readback and test
GLubyte texels[6][4];
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
for (int layer=0; layer<6; ++layer)
{
printf("2D Array Layer %d: [%.3d, %.3d, %.3d, %.3d]\n", layer, int(texels[layer][0]), int(texels[layer][1]), int(texels[layer][2]), int(texels[layer][3]));
}
checkGLError("2D Array - Texture Readback");

// clear resources
glDeleteFramebuffers(1, &amp;fbo);
glDeleteTextures(1, &amp;tex);
checkGLError("2D Array - Resources Destruction");
}


Am I missing something?
After long time looking at the specs and searching the internet for proper sample code or similar problems (and I found someone with the same problem with no solution even in this forum, http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&amp;Number=295703), I am suspecting a driver bug.
The test fails with NVIDIA GTX 560, 275 and 260, 295.73 drivers on Windows 7 64 bits.

Thank you!
Marco.

thokra
03-01-2012, 07:50 AM
Layered rendering to cube maps works fine for me with 295.73 and a GT 540 M.

Shader code please!

Marco Di Benedetto
03-01-2012, 08:45 AM
@thokra

the code is quite large, for me it is enough to know whether the code samples I posted are giving you the correct result (all the printed values should be [0, 0, 0, 0]).

are you saying that you tested my samples and they both worked, or that you have your own cubemap code that works?

thokra
03-01-2012, 08:50 AM
No, I was saying that rendering to all layers of a cube map works for my implementation of cubemap shadow mapping which uses a single cubemap but basically employs the 6 spotlights approach to render contents of each layer.

Edit: Are you using the correct output layout? If you simply write out the vertices to every face and want to solely rely on hardware clipping, you need to set the max_vertices to 18, assuming you're rendering to all 6 faces in any case.

Marco Di Benedetto
03-01-2012, 09:02 AM
are you rendering to all the 6 faces at the same time ("single pass", using a geometry shader and gl_Layer, only a single draw call, attaching the cubemap with glFramebufferTexture()), or are you performing a 6 draw calls ("multipass", attaching a different face to the framebuffer with glFramebufferLayer() in each pass) ?
the multipass approach works just fine also for me, my problem is in the single pass one, where I get only the X+ face written.

I would be thankful if you could test the code I posted :)

thokra
03-01-2012, 09:14 AM
Neither. :)

I attach the cubemap but render to the layer passed to the shader as a uniform which is updated per face. Of course, attaching a single layer is possible, but you get rid of the the API call and the binding by simply explicitly choosing the layer. Basically:

- set uniform indentifying layer to 0
- render spot light view for +X
- set uniform indentifying layer to 1
- render spot light -X
- do 4 more times

Note: The layer IDs may not be correct! Can't look at the code right now.

In the shader it's simply:



uniform int CurrentLayer;

// other stuff ...

void main()
{
gl_Layer = CurrentLayer;
// do stuff ...
}



Edit: On second thought, although you get rid of the glFramebufferLayer() call and the associated work that needs to be done in the server, you have to have to accept a glUniform1i() call. The question is: which way is cheaper?

Marco Di Benedetto
03-01-2012, 09:41 AM
Yeah I understand how you do it (I think you could improve it a little bit by passing all 6 uniforms in an array and do a for loop inside the GS changing gl_Layer, even if this is actually my problem :p).

Maybe I was not clear: my shaders work as expected if I use a GL_TEXTURE_2D_ARRAY with 6 layers, but when I use a GL_TEXTURE_CUBE_MAP (as it should be in my case), same shaders, same fbo configuration (layered rendering using glFramebufferTexture()), I get only the first face written.

In the sample code I posted, for simplicity, I get rid of all shaders stuff, and just do a plain old glClear() to clear all the attached layers (e.g. the 6 cube faces). They all must be cleared with black, unless I did some (to me) unspottable error or my driver/hw is broken.

Any chance to test my code?

I am not calling performances into play, nor discussion about which way (layered single pass, per face multipass, uniforms, instanced rendering, instanced GS, ninja master, whathever ...) is better (as per my experience, almost all variants are veeeeery implementation dependent), I'd just want to know what's wrong with "that" solution :)

thokra
03-01-2012, 09:51 AM
I think you could improve it a little bit by passing all 6 uniforms in an array and do a for loop inside the GS changing gl_Layer[..]

No, that's exactly the point. If I did that I would yank the vertices down to hardware clipping and waste the frustum culling step I do on the app level. All I send is a collection of objects inside current frustum which are rendered using a single or at worst a few glMultiDrawElementsBaseVertex() calls. The shader cannot know which face to select because it cannot deduce which frustum is being rendered - unless you tell it exactly which frustum is currently active, therefore the explicit uniform.


Maybe I was not clear: my shaders work as expected if I use a GL_TEXTURE_2D_ARRAY with 6 layers, but when I use a GL_TEXTURE_CUBE_MAP (as it should be in my case), same shaders, same fbo configuration (layered rendering using glFramebufferTexture()), I get only the first face written.

Yes, you were clear the first time. However, I didn't object because at first sight I didn't see anything wrong with your code and because from what I know, layered rendering should work the same way regardless of the type of layered texture attached to the FBO. In fact, I wouldn't be surprised if the implementation was quite similar. And what good what it do if you had to choose, for instance, 3 different ways to render to a cubemap, a tex array and a 3D texture?


Any chance to test my code?

I'm affraid I can't make the time. :(

Marco Di Benedetto
03-01-2012, 11:59 AM
For everyone that is not comfortable with the "clear only" test case, I wrote another test case that use shader programs. The code uses glew and freeglut.

Here it is:



#include <assert.h>
#include <stdio.h>

#include <GL/glew.h>
#include <GL/freeglut.h>

#define USE_CUBEMAP // if defined, use cube map as render target; otherwise use 6-layers 2D tex array
#define USE_PROGRAM // if defined, use a shader program with a GS that amplificates geometry x6 and redirect each to its layer.

GLuint gTexture = 0;
GLuint gFramebuffer = 0;
GLuint gVertexBuffer = 0; // both VBO and VAO not actually needed (see VS comment below)
GLuint gVertexArray = 0;
#ifdef USE_PROGRAM
GLuint gProgram = 0;
#endif

void checkGLError(const char * msg)
{
const GLenum err = glGetError();
if (err == GL_NO_ERROR) return;
printf("%s: GL ERROR %d\n", msg, int(err));
}

void initTexture(void)
{
const GLubyte colors[6][4] =
{
{ 255, 0, 0, 255 }, // red
{ 0, 255, 0, 255 }, // green
{ 0, 0, 255, 255 }, // blue
{ 0, 255, 255, 255 }, // cyan
{ 255, 0, 255, 255 }, // magenta
{ 255, 255, 0, 255 } // yellow
};

GLuint tex = 0;
glGenTextures(1, &amp;tex);

#ifdef USE_CUBEMAP
glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
for (int face=0; face<6; ++face)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors[face]);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
#else
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
#endif

gTexture = tex;
}

void initFramebuffer(void)
{
GLuint fbo = 0;
glGenFramebuffers(1, &amp;fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, gTexture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
assert(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFE R) == GL_FRAMEBUFFER_COMPLETE);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

gFramebuffer = fbo;
}

void initVertexBuffer(void)
{
const GLfloat point[3] = { 0.0f, 0.0f, 0.0f };

GLuint vbo = 0;
glGenBuffers(1, &amp;vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 3 * sizeof(GLfloat), point, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

gVertexBuffer = vbo;
}

void initVertexArray(void)
{
GLuint vao = 0;
glGenVertexArrays(1, &amp;vao);
glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, gVertexBuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

gVertexArray = vao;
}

#ifdef USE_PROGRAM
void initProgram(void)
{
// no actual need for an input VS attribute and a feeding VAO with a VBO
// (glDrawArrays() would anyway trigger VS execution);
// they are used only to avoid anything that could *seem* a mistake.
const char * vsrc =
"#version 330 core\n"
"\n"
"in vec3 aPosition;\n"
"\n"
"out vec3 vPosition;\n"
"\n"
"void main(void)\n"
"{\n"
" vPosition = aPosition;\n"
"}\n";

const char * gsrc =
"#version 330 core\n"
"\n"
"layout(points) in;\n"
"layout(points, max_vertices = 6) out;\n"
"\n"
"in vec3 vPosition[1];\n"
"\n"
"void main(void)\n"
"{\n"
" vec4 position = vec4(vPosition[0], 1.0);\n"
" for (int i=0; i<6; ++i)\n"
" {\n"
" gl_Layer = i;\n"
" gl_Position = position;\n"
" EmitVertex();\n"
" EndPrimitive();\n"
" }\n"
"}\n";

const char * fsrc =
"#version 330 core\n"
"\n"
"layout(location = 0) out vec4 oColor;\n"
"\n"
"void main(void)\n"
"{\n"
" oColor = vec4(0.0);\n"
"}\n";

GLuint vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &amp;vsrc, 0);

GLuint gs = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(gs, 1, &amp;gsrc, 0);

GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, &amp;fsrc, 0);

GLuint program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, gs);
glAttachShader(program, fs);
glLinkProgram(program);

char log[2048];
glGetProgramInfoLog(program, 2048, 0, log);
printf(log);

// flagged as deleted, will be deleted when program will be deleted
glDeleteShader(vs);
glDeleteShader(gs);
glDeleteShader(fs);

gProgram = program;
}
#endif

void draw(void)
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gFramebuffer);

glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

#ifdef USE_PROGRAM
glViewport(0, 0, 1, 1);

glBindVertexArray(gVertexArray);
glUseProgram(gProgram);
glDrawArrays(GL_POINTS, 0, 1);
glUseProgram(0);
glBindVertexArray(0);
#endif

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

void readback(void)
{
// if USE_PROGRAM is defined, all layers must be black (written by frag shader).
// otherwise they must match the clear color (white).
#ifdef USE_CUBEMAP
GLubyte texel[4];
glBindTexture(GL_TEXTURE_CUBE_MAP, gTexture);
for (int face=0; face<6; ++face)
{
glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, GL_UNSIGNED_BYTE, texel);
printf("CubeMap Face %d: [%.3d, %.3d, %.3d, %.3d]\n", face, int(texel[0]), int(texel[1]), int(texel[2]), int(texel[3]));
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
#else
GLubyte texels[6][4];
glBindTexture(GL_TEXTURE_2D_ARRAY, gTexture);
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
for (int layer=0; layer<6; ++layer)
{
printf("2D Array Layer %d: [%.3d, %.3d, %.3d, %.3d]\n", layer, int(texels[layer][0]), int(texels[layer][1]), int(texels[layer][2]), int(texels[layer][3]));
}
#endif
}

void cleanup(void)
{
#ifdef USE_PROGRAM
glDeleteProgram(gProgram);
#endif
glDeleteVertexArrays(1, &amp;gVertexArray);
glDeleteBuffers(1, &amp;gVertexBuffer);
glDeleteFramebuffers(1, &amp;gFramebuffer);
glDeleteTextures(1, &amp;gTexture);
}

void test(void)
{
glewInit();

glGetError(); // flush error

#ifdef USE_PROGRAM
initProgram();
checkGLError("Program");
#endif

initTexture();
checkGLError("Texture");

initFramebuffer();
checkGLError("Framebuffer");

initVertexBuffer();
checkGLError("VertexBuffer");

initVertexArray();
checkGLError("VertexArray");

draw();
checkGLError("Draw");

readback();
checkGLError("Readback");

cleanup();
checkGLError("Cleanup");
}

int main(int argc, char ** argv)
{
glutInit(&amp;argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL | GLUT_DOUBLE);

glutInitWindowSize(256, 256);
glutInitWindowPosition(0, 0);
glutCreateWindow("OpenGL Test");

glutInitContextVersion(3, 3);
glutInitContextProfile(GLUT_CORE_PROFILE);
glutInitContextFlags(GLUT_FORWARD_COMPATIBLE | GLUT_DEBUG);

test();

return 0;
}


Any idea about what's wrong?

Marco Di Benedetto
03-05-2012, 05:07 AM
Sorry for pinging, can someone point me in the right direction?
horrible mistake of mine? driver bug?

(@thokra: thanks anyway for your replies! :))