What layout and binding do i have to do to make a (working) shader storage buffer readable in a second shader program?
I set up and populated a SSBO which i bound successfully and used in a geometry shader. That shader reads and writes to that SSBO - no problems so far. No rendering done there.
In the next step, my rendering pass (second shader program) shall have access to this data. The idea is to have a big data set while the vertex shader of the second program only uses some indices per render call to pick certain values of that SSBO.
Do i miss some specific binding commands or did i place them at the wrong spot?
Is the layout consistent in both programs? Did i mess up the instances?
I just can't find any examples of a SSBO used in two programs..
Creating, populating and binding:
float data[48000];
data[0] = -1.0;
data[1] = 1.0;
data[2] = -1.0;
data[3] = -1.0;
data[4] = 1.0;
data[5] = -1.0;
data[6] = 1.0;
data[7] = 1.0;
data[16000] = 0.0;
data[16001] = 1.0;
data[16002] = 0.0;
data[16003] = 0.0;
data[16004] = 1.0;
data[16005] = 0.0;
data[16006] = 1.0;
data[16007] = 1.0;
GLuint ssbo;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(data), &data, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo);
Instancing in geometry shader
layout(std140, binding = 1) buffer mesh
{
vec2 points[8000];
vec2 texs[8000];
vec4 colors_and_errors[8000];
} mesh_data;
Second instance in the vertex shader of the other program
layout(std140, binding = 1) buffer mesh
{
vec2 points[8000];
vec2 texs[8000];
vec4 colors_and_errors[8000];
} mesh_data;
Are the instances working against each other?
Right now am not posting my bindings done in the render loop since i am not sure what i am doing there. I tried to bind before/ after changing the used program; without success.
Does anybody have an idea?
EDIT: Do i also have to bind the SSBO to the second program outside of the render loop? In a different way than the first binding?
EDIT: Although i did not solve this particular problem, i found a work-around that might be even more in the sense of opengl.
I used the SSBO of the first program as vertex attributes in the second program. This and the indexed-rendering function of opengl solved this issue.
(Should this be marked as solved?)
It seems like you're most of the way there, but there are a few things you should watch out for.
Is the layout consistent in both programs?
layout(std140, binding = 1) buffer mesh
You need to be careful about this layout. std140 will round up alignments to vec4, so will no longer line up with the data you're providing from the C code. In this case, std430 should work for you.
Do i also have to bind the SSBO to the second program outside of the render loop? In a different way than the first binding?
Once you've bound the SSBO once, assuming both programs are using the same binding point (in your example, they are) then you should be fine. Sharing data between programs is fine, but synchronisation is required. You can enforce this with a memory barrier.
You don't mention VAOs, but you will only be able to use SSBOs after you've bound a VAO (not on the default one).
I think this might be best explained with an example.
Vertex shader for the first program. It uses the buffer data for its position and texture coords and then flips the positions in Y.
layout(std430, binding = 1) buffer mesh {
vec4 points[3];
vec2 texs[3];
} mesh_data;
out highp vec2 coords;
void main() {
coords = mesh_data.texs[gl_VertexID];
gl_Position = mesh_data.points[gl_VertexID];
mesh_data.points[gl_VertexID] = vec4(gl_Position.x, -gl_Position.y, gl_Position.zw);
}
Verted shader for the second program. It just uses the data but doesn't modify it.
layout(std430, binding = 1) buffer mesh {
vec4 points[3];
vec2 texs[3];
} mesh_data;
out highp vec2 coords;
void main() {
coords = mesh_data.texs[gl_VertexID];
gl_Position = mesh_data.points[gl_VertexID];
}
In the application, you need to bind a VAO.
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
Then setup your SSBO.
float const data[] = {
-0.5f, -0.5f, 0.0f, 1.0,
0.0f, 0.5f, 0.0f, 1.0,
0.5f, -0.5f, 0.0f, 1.0,
0.0f, 0.0f,
0.5f, 1.0f,
1.0f, 0.0f
};
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(data), data, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo);
Make the draw calls using the first program.
glUseProgram(first_program);
glDrawArrays(GL_TRIANGLES, 0, 3);
Insert a memory barrier to ensure the writes complete from the preceding draw call before the next draw call tries to read from the buffer.
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
Make the draw calls using the second program.
glUseProgram(second_program);
glDrawArrays(GL_TRIANGLES, 0, 3);
I hope that clarifies things! Let me know if you have any further questions.
Related
I'm pretty new to 3D programming. I'm trying to learn OpenGL from this site. During the reading I couldn't really understand how the layout (location = 0) line really operates. I've tried to search for other explanation online both in the OpenGL wiki and in other sites, and I've managed to find this site from which I understood a little more.
So if I am correct the vertex shader takes some inputs and generates some outputs. The input of the shader are called vertex attributes and each one of them as an index location called attribute index. Now I expect that if the shader takes as input a single vertex and its attributes, it has to run multiple times, one for each vertex of the object I'm trying to render.
Is it correct what I wrote up until this point?
Now, what I didn't manage to understand is how layout (location = 0) really works. My assumption is that this intruction needs to tell the shader from where location in memory to pick the first index attribute. Thus each time the shader re-runs (if it actually re-runs), the location should move by one unit, like in a normal for loop. Is this interpretation correct? And, please, can anyone actually explain me, in an organic way, how the vertex shader operates?
P.S. Thank you in advance for your time and excuse my poor English: I'm still practising it!
Edit
This is the code. Following the first guide I linked I created an array of vertices:
float vertices[] {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
then I created a vertex buffer object:
unsigned int VBO;
glGenBuffer(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
I added the data to the VBO:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
while the vertex shader reads:
#version 330 core
layout (location = 0) in vec3 aPos:
void main() {
gl_Position(aPos.x, aPos.y, aPos.z, 1.0f);
}
You need to look at both sides of this. You bind a buffer containing all of your data. Say position and color.
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
Now in the program, I can use these vectors without specifying the index of the vertex I am processing because we had to tell GL how to buffer the data.
We do that when we bind buffers to the program.
Lets say we want to create a triangle. It has 3 vertexes, each vertex has two attributes: color and position. We create a vertex shader that processes each vertex, in that program it is implied that each vertex has a color and position. You don't care about the index in the array it is (for now).
The program will take vertex i, v_i and process it. How it populates position and vector depend on how you bind the data. I could have two arrays,
positionData = [x0, y0, z0, x1, ... z3];
colorData = [r0, g0, b0, r1, ... b3];
So I would buffer this data, then I would bind that buffer to the program at the attribute location and specify how it is read. Eg. bind the positionBuffer to attribute location 0, read it in strides of three with no offset.
The same with the color data, but with location 1.
Alternatively I could do.
posColData = [ x0, y0, z0, r0, g0, b0, x1, y1, ... b3];
Then I would create posColBuffer and bind it to the 0th attribute, with a stride of 6. I would also bind the posColBuffer to the 1st attribute with a stride of 6 and an offset of 3.
The code you are using does this here.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); ;
They utilize the layout clause by just saying 0 since they know the location.
I am trying to figure out how SSBO works with a very basic example. The vertex shader:
#version 430
layout(location = 0) in vec2 Vertex;
void main() {
gl_Position = vec4(Vertex, 0.0, 1.0);
}
And the fragment shader:
#version 430
layout(std430, binding = 2) buffer ColorSSBO {
vec3 color;
};
void main() {
gl_FragColor = vec4(color, 1.0);
}
I know they work because if I replace vec4(color, 1.0) with vec4(1.0, 1.0, 1.0, 1.0) I see a white triangle in the center of the screen.
I initialize and bind the SSBO with the following code:
GLuint ssbo;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
float color[] = {1.f, 1.f, 1.f};
glBufferData(GL_SHADER_STORAGE_BUFFER, 3*sizeof(float), color, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
What is wrong here?
My guess is that you are missing the SSBO binding before rendering. In your example, you are copying the content and then you bind it immediately, which is unnecessary for the declaration. In other words, the following line in your example:
...
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssbo);
...
Must be placed before rendering, such as:
...
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssbo);
/*
Your render calls and other bindings here.
*/
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
...
Without this, your shader (theoretically) will not be able to see the content.
In addition, as Andon M. Coleman has suggested, you have to use padding for your elements when declaring arrays (e.g., use vec4 instead of vec3). If you don't, it will apparently work but produce strange results because of this fact.
The following two links have helped me out understanding the meaning of an SSBO and how to handle them in your code:
https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object
http://www.geeks3d.com/20140704/tutorial-introduction-to-opengl-4-3-shader-storage-buffers-objects-ssbo-demo/
I hope this helps to anyone facing similar issues!
P.S.: I know this post is old, but I wanted to contribute.
When drawing a triangle, three points are necessary, and 3 separate sets of red green blue values are required for each point. You are only putting one set into the shader buffer. For the other two points, the value of color drops to the default, which is black (0.0,0.0,0.0). If you don't have blending enabled, it is likely that the triangle is being painted completely black because two of its vertices are black.
Try putting 2 more sets of red green blue values into the storage buffer to see it will load them as color values for the other two points.
My use case is a bunch a textured quads that I want to draw. I'm trying to use the same indexed array of a quad to draw it a bunch of times and use the gl_InstanceID and gl_VertexID in GLSL to retrieve texture and position info from a Texture Buffer.
The way I understand a Texture Buffer is that I create it and my actual buffer, link them, and then whatever I put in the actual buffer magically appears in my texture buffer?
So I have my vertex data and index data:
struct Vertex
{
GLfloat position[4];
GLfloat uv[2];
};
Vertex m_vertices[4] =
{
{{-1,1,0,1},{0,1}},
{{1,1,0,1},{1,1}},
{{-1,-1,0,1},{0,0}},
{{1,-1,0,1},{1,0}}
};
GLuint m_indices[6] = {0,2,1,1,2,3};
Then I create my VAO, VBO and IBO for the quads:
glGenBuffers(1,&m_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER,m_vertexBuffer);
glBufferData(GL_ARRAY_BUFFER,sizeof(Vertex)*4,&m_vertices,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
glGenVertexArrays(1,&m_vao);
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER,m_vertexBuffer);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,4,GL_FLOAT, GL_FALSE, sizeof(struct Vertex),(const GLvoid*)offsetof(struct Vertex, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(0,2,GL_FLOAT, GL_FALSE, sizeof(struct Vertex),(const GLvoid*)offsetof(struct Vertex, uv));
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(m_vao);
glGenBuffers(1, &m_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*6,&m_indices,GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
glBindVertexArray(0);
I'm pretty sure that I've done the above correctly. My quads have 4 vertices, with six indexes to draw triangles.
Next I create my buffer and texture for the the Texture Buffer:
glGenBuffers(1,&m_xywhuvBuffer);
glBindBuffer(GL_TEXTURE_BUFFER, m_xywhuvBuffer);
glBufferData(GL_TEXTURE_BUFFER, sizeof(GLfloat)*8*100, nullptr, GL_DYNAMIC_DRAW); // 8 floats
glGenTextures(1,&m_xywhuvTexture);
glBindTexture(GL_TEXTURE_BUFFER, m_xywhuvTexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, m_xywhuvBuffer); // they're in pairs of 2, in r,g of each texel.
glBindBuffer(GL_TEXTURE_BUFFER,0);
So, the idea is that every four texels belongs to one quad, or gl_InstanceID.
When I'm drawing my quads, they execute the below:
glActiveTexture(GL_TEXTURE0);
glBindBuffer(GL_TEXTURE_BUFFER, m_xywhuvBuffer);
std::vector<GLfloat> xywhuz =
{
-1.0f + position.x / screenDimensions.x * 2.0f,
1.0f - position.y / screenDimensions.y * 2.0f,
dimensions.x / screenDimensions.x,
dimensions.y / screenDimensions.y,
m_region.x,
m_region.y,
m_region.w,
m_region.h
};
glBufferSubData(GL_TEXTURE_BUFFER, sizeof(GLfloat)*8*m_rectsDrawnThisFrame, sizeof(GLfloat)*8, xywhuz.data());
m_rectsDrawnThisFrame++;
So I increase m_rectsDrawThisFrame for each quad. You'll notice that the data I'm passing is 8 GLfloats, so each of the 4 texels that belong to each gl_InstanceID is the x,y position, the width and height, and then the same details for the real texture that I'm going to texture my quads with.
Finally once all of my rects have updated their section of the GL_TEXTURE_BUFFER I run this:
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D,texture); // this is my actual texture that the quads take a section from to texture themselves.
glUniform1i(m_program->GetUniformLocation("tex"),1);
glUniform4f(m_program->GetUniformLocation("color"),1,0,1,1);
glBindVertexArray(m_vao);
glDrawElementsInstanced(GL_TRIANGLES,4,GL_UNSIGNED_INT,0,m_rectsDrawnThisFrame);
m_rectsDrawnThisFrame = 0;
I reset the draw count. I also noticed that I had to activate the texture in the second slot. Does the Texture Buffer Object use up one?
Finally my Vert shader
#version 410
layout (location = 0) in vec4 in_Position;
layout (location = 1) in vec2 in_UV;
out vec2 ex_texcoord;
uniform samplerBuffer buf;
void main(void)
{
vec2 position = texelFetch(buf,gl_InstanceID*4).xy;
vec2 dimensions = texelFetch(buf,gl_InstanceID*4+1).xy;
vec2 uvXY = texelFetch(buf,gl_InstanceID*4+2).xy;
vec2 uvWH = texelFetch(buf,gl_InstanceID*4+3).xy;
if(gl_VertexID == 0)
{
gl_Position = vec4(position.xy,0,1);
ex_texcoord = uvXY;
}
else if(gl_VertexID == 1)
{
gl_Position = vec4(position.x + dimensions.x, position.y,0,1);
ex_texcoord = vec2(uvXY.x + uvWH.x, uvXY.y);
}
else if(gl_VertexID == 2)
{
gl_Position = vec4(position.x, position.y + dimensions.y, 0,1);
ex_texcoord = vec2(uvXY.x, uvXY.y + uvWH.y);
}
else if(gl_VertexID == 3)
{
gl_Position = vec4(position.x + dimensions.x, position.y + dimensions.y, 0,1);
ex_texcoord = vec2(uvXY.x + uvWH.x, uvXY.y + uvWH.y );
}
}
And my Frag shader
#version 410
in vec2 ex_texcoord;
uniform sampler2D tex;
uniform vec4 color = vec4(1,1,1,1);
layout (location = 0) out vec4 FragColor;
void main()
{
FragColor = texture(tex,ex_texcoord) * color;
}
Now the problem, after I'm getting no errors reported in GLIntercept, is that I'm getting nothing drawn on the screen.
Any help?
There is one subtle issue in your code that would certainly stop it from working. At the end of the VAO/VBO setup code, you have this:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
glBindVertexArray(0);
The GL_ELEMENT_ARRAY_BUFFER binding is part of the VAO state. If you unbind it while the VAO is bound, this VAO will not have an element array buffer binding. Which means that you don't have indices when you draw later.
You should simply remove this call:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
Also, since you have 6 indices, the second argument to the draw call should be 6:
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, m_rectsDrawnThisFrame);
Apart from that, it all looks reasonable to me. But there's quite a lot of code, so I can't guarantee that I would have spotted all problems.
I also noticed that I had to activate the texture in the second slot. Does the Texture Buffer Object use up one?
Yes. The buffer texture needs to be bound, and the value of the sampler variable set to the corresponding texture unit. Since you bind the buffer texture during setup, never unbind it, and the default value of the sampler variable is 0, you're probably fine there. But I think it would be cleaner to set it up more explicitly. Where you prepare for drawing:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_xywhuvTexture);
glUniform1i(m_program->GetUniformLocation("buf"), 0);
I am trying to use VBO and Instancing mechanism the most efficent way. I have a world based on voxels and I would like to draw them using least possible number of draw-calls. The code below prepares VBO with a quad:
void VoxelView::initVBOs() {
/*--------------------- Main OpenGL Program ---------------------*/
/* Vertices of a triangle (counter-clockwise winding) */
float data[6][3] = {
// Left bottom triangle
{ -0.5f, 0.5f, 0.0f },
{ -0.5f, -0.5f, 0.0f },
{ 0.5f, -0.5f, 0.0f },
// Right top triangle
{ 0.5f, -0.5f, 0.0f },
{ 0.5f, 0.5f, 0.0f },
{ -0.5f, 0.5f, 0.0f }
};
/*---------------------- Initialise VBO - (Note: do only once, at start of program) ---------------------*/
/* Create a new VBO and use the variable "triangleVBO" to store the VBO id */
glGenBuffers(1, &triangleVBO);
/* Make the new VBO active */
glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
/* Upload vertex data to the video device */
glBufferData(GL_ARRAY_BUFFER, 6 * 3 * sizeof(float), data, GL_STATIC_DRAW);
/* Specify that our coordinate data is going into attribute index 0(shaderAttribute), and contains three floats per vertex */
glVertexAttribPointer(shaderAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
/* Enable attribute index 0(shaderAttribute) as being used */
glEnableVertexAttribArray(shaderAttribute);
/* Make the new VBO active. */
glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
/*-------------------------------------------------------------------------------------------------------*/
/*--------------------- Load Vertex and Fragment shaders from files and compile them --------------------*/
/* Read our shaders into the appropriate buffers */
vertexSource = filetobuf("Shaders/exampleVertexShader1.vert");
fragmentSource = filetobuf("Shaders/exampleFragmentShader1.frag");
/* Assign our handles a "name" to new shader objects */
vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
/* Associate the source code buffers with each handle */
glShaderSource(vertexShader, 1, (const GLchar**)&vertexSource, 0);
glShaderSource(fragmentShader, 1, (const GLchar**)&fragmentSource, 0);
/* Free the temporary allocated memory */
free(vertexSource);
free(fragmentSource);
/* Compile our shader objects */
glCompileShader(vertexShader);
glCompileShader(fragmentShader);
/*-------------------------------------------------------------------------------------------------------*/
/*-------------------- Create shader program, attach shaders to it and then link it ---------------------*/
/* Assign our program handle a "name" */
shaderProgram = glCreateProgram();
/* Attach our shaders to our program */
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
/* Bind attribute index 0 (shaderAttribute) to in_Position*/
/* "in_Position" will represent "data" array's contents in the vertex shader */
glBindAttribLocation(shaderProgram, shaderAttribute, "in_Position");
/* Link shader program*/
glLinkProgram(shaderProgram);
I am rendering the quad the following way:
void VoxelView::renderVBO()
{
/* Set shader program as being actively used */
glUseProgram(shaderProgram);
/* Set background colour to BLACK */
glClearColor(0.0, 0.0, 0.0, 1.0);
/* Clear background with BLACK colour */
glClear(GL_COLOR_BUFFER_BIT);
/* Actually draw the triangle, giving the number of vertices provided by invoke glDrawArrays
while telling that our data is a triangle and we want to draw 0-3 vertexes
*/
glDrawArrays(GL_TRIANGLES, 0, 6);
}
I would like to draw this quad (which uses VBO) multiple times using the instancing mechanizm. I would like it to fe fairly simple as I want to implement it for more sophisticated code. I know that I should use glDrawElementsInstanced method to use instancing but I don't know how to do it. Does anybody know how to do it?
When using glDrawElementsInstanced you need to make your shaders use gl_InstanceiD
#version 410
layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in mat4 WVP;
layout (location = 7) in mat4 World;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
flat out int InstanceID;
void main()
{
gl_Position = WVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = World * vec4(Normal, 0.0)).xyz;
WorldPos0 = World * vec4(Position, 1.0)).xyz;
InstanceID = gl_InstanceID;
};
glDrawElementsInstanced use gl_InstanceID variable as if it were a static integer vertex attribute. When the first copy of the vertices is sent to OpenGL,
gl_InstanceID will be zero. It will then be incremented once for each copy of the geometry and will eventually reach instanceCount - 1.
It behaves like this
for (int n = 0; n < instancecount; n++)
{
// Set the value of gl_InstanceID
glVertexAttrib1i(gl_InstanceID, n); // except this is internally incremented and not a real vertex attribute
// Make a normal call to glDrawElements
glDrawElements(mode, count, type, indices);
}
Except that this happens internally, and you only need to call glDrawArraysInstanced() once. In order to use different transforms for each instance you can pass an array of uniform matrices and index that using gl_InstanceID or you can use texture buffer objects and store transforms there.
So your drawing code should become like this, but note that you still need to pass uniform matrices for each intsance in an array or pass a texture buffer object once for all.
glDrawElementsInstanced(primitivetype, indices, GL_UNSIGNED_INT, 0, instanceCount-1);
and your vertex shader should become
uniform mat4 instancematrices[32];
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * instancematrices[gl_InstanceID] * gl_Vertex;
}
I created a class that renders videoframes (on Mac) to a custom framebuffer object. As input I have a YUV texture, and I successfully created a fragment shader, which takes as input 3 rectangle textures (one for Y, U, and V planes each, the data for which is uploaded by glTexSubImage2D using GL_TEXTURE_RECTANGLE_ARB, GL_LUMINANCE and GL_UNSIGNED_BYTE), before rendering I set the active textures to three different texture units (0, 1 and 2) and bind a texture for each, and for performance reasons I used GL_APPLE_client_storage and GL_APPLE_texture_range. Then I rendered it using glUseProgram(myProg), glBegin(GL_QUADS) ... glEnd().
That worked fine, and I got the expected result (aside from a flickering effect, which I guess has to do with the fact that I used two different GL contexts on two different threads, and I suppose they get into each other's way at some point [that's topic for another question later]). Anyway, I decided to further improve my code by adding a vertex shader as well, so that I can skip the glBegin/glEnd - which I read is outdated and should be avoided anyway.
So as a next step I created two buffer objects, one for the vertices and one for the texture coordinates:
const GLsizeiptr posSize = 4 * 4 * sizeof(GLfloat);
const GLfloat posData[] =
{
-1.0f, -1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f, 1.0f,
1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f
};
const GLsizeiptr texCoordSize = 4 * 2 * sizeof(GLfloat);
const GLfloat texCoordData[] =
{
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
};
glGenBuffers(1, &m_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, posSize, posData, GL_STATIC_DRAW);
glGenBuffers(1, &m_texCoordBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glBufferData(GL_ARRAY_BUFFER, texCoordSize, texCoordData, GL_STATIC_DRAW);
Then after loading the shaders I try to retrieve the locations of the attributes in the vertex shader:
m_attributeTexCoord = glGetAttribLocation( m_shaderProgram, "texCoord");
m_attributePos = glGetAttribLocation( m_shaderProgram, "position");
which gives me 0 for texCoord and 1 for position, which seems fine.
After getting the attributes I also call
glEnableVertexAttribArray(m_attributePos);
glEnableVertexAttribArray(m_attributeTexCoord);
(I am doing that only once, or does it have to be done before every glVertexAttribPointer and glDrawArrays? Does it need to be done per texture unit? or while my shader is activated with glProgram? Or can I do it just anywhere?)
After that I changed the rendering code to replace the glBegin/glEnd:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_Y);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_U);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_V);
glUseProgram(myShaderProgID);
// new method with shaders and buffers
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glVertexAttribPointer(m_attributePos, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glVertexAttribPointer(m_attributeTexCoord, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glDrawArrays(GL_QUADS, 0, 4);
glUseProgram(0);
But since changing the code to this, I always only ever get a black screen as result. So I suppose I am missing some simple steps, maybe some glEnable/glDisable or setting some things properly - but but like I said I am new to this, so I haven't really got an idea. For your reference, here is the vertex shader:
#version 110
attribute vec2 texCoord;
attribute vec4 position;
// the tex coords for the fragment shader
varying vec2 texCoordY;
varying vec2 texCoordUV;
//the shader entry point is the main method
void main()
{
texCoordY = texCoord;
texCoordUV = texCoordY * 0.5; // U and V are only half the size of Y texture
gl_Position = gl_ModelViewProjectionMatrix * position;
}
My guess is that I am missing something obvious here, or just don't have a deep enough understanding of the ongoing processes here yet. I tried using OpenGLShaderBuilder as well, which helped me get the original code for the fragment shader right (this is why I haven't posted it here), but since adding the vertex shader it doesn't give me any output either (was wondering how it could know how to produce the output, if it doesn't know the position/texCoord attributes anyway?)
I haven't closely studied every line, but I think your logic is mostly correct. What I see missing is glEnableVertexAttribArray. You need to enable both vertex attributes before the glDrawArrays call.