I'm trying to minimize memory fragmentation on the GPU by storing all vertex data in the same VBO.
After parsing the .obj nodes with Assimp and extracting all the mesh data, I've alloc'd two large buffers that can contain everything and dumped everything in them (one vertex array and another indices array). Rendering all the data works fine with glDrawArrays, but this is not the result I'm trying to obtain.
struct vertex_data
{
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};
uint32_t TotalVertices = 0;
uint32_t TotalIndices = 0;
uint32_t TotalMeshes = Model->NextMeshToLoad;
if (TotalMeshes != 0)
{
for (uint32_t MeshOffset = 0;
MeshOffset < TotalMeshes;
++MeshOffset)
{
mesh *Mesh = Model->Meshes + MeshOffset;
uint32_t MeshTotalVertices = Mesh->NextVertexData;
uint32_t MeshTotalIndices = Mesh->NextIndice;
TotalVertices += MeshTotalVertices;
TotalIndices += MeshTotalIndices;
}
vertex_data *CombinedVertexData = PushArray(Arena, TotalVertices, vertex_data);
uint32_t *CombinedIndices = PushArray(Arena, TotalIndices, uint32_t);
uint32_t CombinedVertexDataOffset = 0;
uint32_t CombinedIndicesOffset = 0;
for (uint32_t MeshOffset = 0;
MeshOffset < TotalMeshes;
++MeshOffset)
{
mesh *Mesh = Model->Meshes + MeshOffset;
uint32_t MeshTotalVertices = Mesh->NextVertexData;
memcpy_s(CombinedVertexData + CombinedVertexDataOffset, TotalVertices * sizeof(vertex_data),
Mesh->VertexData, MeshTotalVertices * sizeof(vertex_data));
CombinedVertexDataOffset += MeshTotalVertices;
uint32_t MeshTotalIndices = Mesh->NextIndice;
memcpy_s(CombinedIndices + CombinedIndicesOffset, TotalIndices * sizeof(uint32_t),
Mesh->Indices, MeshTotalIndices * sizeof(uint32_t));
CombinedIndicesOffset += MeshTotalIndices;
}
Model->CombinedVertexData = CombinedVertexData;
Model->CombinedIndicesData = CombinedIndices;
Model->CombinedVertexDataSize = TotalVertices;
Model->CombinedIndicesDataSize = TotalIndices;
Model->DataStatus = model_data_status::PENDING_UPLOAD;
Model->NextMeshToLoad stores the next empty array location in which it can store a mesh. It also represents the current size of stored meshes - not to be confounded with the maximum size alloc'd for the array.
Mesh->NextVertexData and Mesh->NextIndice work the same, but for each mesh in part.
Uploading the data to the GPU:
if (Model->DataStatus == model_data_status::PENDING_UPLOAD)
{
glGenVertexArrays(1, &Model->VertexArrayObject);
glGenBuffers(1, &Model->VertexBufferObject);
glGenBuffers(1, &Model->ElementBufferObject);
glBindVertexArray(Model->VertexArrayObject);
glBindBuffer(GL_ARRAY_BUFFER, Model->VertexBufferObject);
glBufferData(GL_ARRAY_BUFFER, Model->CombinedVertexDataSize * sizeof(vertex_data), Model->CombinedVertexData, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Model->ElementBufferObject);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, Model->CombinedIndicesDataSize * sizeof(uint32_t), &Model->CombinedIndicesData[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex_data), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex_data), (void *)(sizeof(glm::vec3)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_data), (void *)(2 * sizeof(glm::vec3)));
glBindVertexArray(0);
}
The problem is that each mesh has a different/separate material. This forces me to issue separate draw calls for each mesh (need to set different uniform properties + bind different textures). I know the vertex and indices size for each mesh in part, so I figured I could just call glDrawElements and use the 4th parameter as an offset into the ELEMENT_ARRAY_BUFFER:
glBindVertexArray(Model->VertexArrayObject);
uint32_t IndicesOffset = 0;
uint32_t MeshesSize = Model->NextMeshToLoad;
for (uint32_t MeshIndex = 0;
MeshIndex < MeshesSize;
++MeshIndex)
{
mesh *Mesh = Model->Meshes + MeshIndex;
uint32_t IndicesDataSize = Mesh->NextIndice;
glDrawElements(GL_TRIANGLES, IndicesDataSize, GL_UNSIGNED_INT, (void *)(IndicesOffset * sizeof(uint32_t)));
IndicesOffset += Mesh->NextIndice;
}
glBindVertexArray(0);
Unfortunately this doesn't seem to work... My data is only partially rendered and I can't understand why this is happening. I've checked the data loaded from the .obj file and it's fine. I've also checked the combined vertex data & the combined indices data... it's all there. But my meshes aren't all there on the screen :( (I've enabled wireframe mode so I can better visualize the issue)
If I try to call glDrawArrays on the combined vertex data, it's all working fine and dandy:
At this moment, both the combined vertex and indices arrays register a size of 57174 elements each. Is it not possible for OpenGL to properly map the indices offset to the vertices data internally and properly render? If I use one VBO + EBO per mesh, everything renders fine, so I know the data I'm receiving is not corrupted (further evidenced by glDrawArrays on the combined vertex data). I've been reading and trying all sorts of solutions without anything working... Pretty much at my wits end. Would really appreciate some help! Thanks!
Related
I try to use 2 VBO inside a VAO and I end up with a crash (far beyond my app).
The idea is to make a first VBO (and optionnally an IBO) to stucture the geometry.
This worked well, until I get the idea to add a second VBO for the model matrix as a vertex attribute instead of an uniform.
So, when I declare my mesh I do as follow (reduced code) :
GLuint vao = 0;
glCreateVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo = 0;
glCreateBuffers(1, &vbo);
glNamedBufferStorage(vbo, ...); // Fill the right data ...
for ( ... my attributes ) // Position, normal, texcoords ...
{
glVertexArrayAttribFormat(vao, attribIndex, size, GL_FLOAT, GL_FALSE, relativeOffset);
glVertexArrayAttribBinding(vao, attribIndex, bindingIndex);
glEnableVertexArrayAttrib(vao, attribIndex);
} -> this gives me the "stride" parameter for below.
glVertexArrayVertexBuffer(vao, 0/*bindingindex*/, vbo, 0, stride/*Size of one element in vbo in bytes*/);
GLuint ibo = 0;
glCreateBuffers(1, &ibo);
glNamedBufferStorage(ibo, ...); // Fill the right data ...
glVertexArrayElementBuffer(vao, ibo);
Until there, everything is fine, all I have to do is to call glBindVertexArray() and a glDrawXXX() command, I have something perfect on screen.
So, I decided to remove the modelMatrix uniform from the shader to use a mat4 attribute,
I could have choose an UBO instead but I want to extend the idea to instancing rendering by providing several matrices.
So, I tested with one model matrix in a VBO and just before the rendering, I do as follow (the VBO is built the same way before, I just put 16 floats for an identity matrix) :
glBindVertexArray(theObjectVAOBuiltBefore);
const auto bindingIndex = static_cast< GLuint >(1); // Here next binding point for the VBO, I guess...
const auto stride = static_cast< GLsizei >(16 * sizeof(GLfloat)); // The stride is the size in bytes of a matrix
glVertexArrayVertexBuffer(theObjectVAOBuiltBefore, bindingIndex, m_vertexBufferObject.identifier(), 0, stride); // I add the new VBO to the currentle VAO which have already a VBO (bindingindex 0) and an IBO
// Then I describe my new VBO as a matrix of 4 vec4.
const auto size = static_cast< GLint >(4);
for ( auto columnIndex = 0U; columnIndex < 4U; columnIndex++ )
{
const auto attribIndex = static_cast< unsigned int >(VertexAttributes::Type::ModelMatrix) + columnIndex;
glVertexArrayAttribFormat(theObjectVAOBuiltBefore, attribIndex, size, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(theObjectVAOBuiltBefore, attribIndex, bindingIndex);
glEnableVertexArrayAttrib(theObjectVAOBuiltBefore, attribIndex);
glVertexAttribDivisor(attribIndex, 1); // Here I want this attribute per instance.
}
glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr, 1);
And the result is a beautiful crash, I don't have any clue because the crash occurs within the driver where I can't have a debug output.
Is my idea a complete garbage ? Or there is something I missed ?
I found the error glVertexAttribDivisor() is part of the old ways (like glVertexAttribPointer(), ...), I switched to glVertexBindingDivisor()/glVertexArrayBindingDivisor() and now there is no crash at all.
Answers were there : https://www.khronos.org/opengl/wiki/Vertex_Specification#Separate_attribute_format
I am using the following library to render text in OpenGL: fontstash. I have another header file which adds support for OpenGL 3.0+. The question is why is the render implementation with core profile much slower than the immediate mode?
Here is the render code with immediate mode:
static void glfons__renderDraw(void* userPtr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts)
{
GLFONScontext* gl = (GLFONScontext*)userPtr;
if (gl->tex == 0) return;
glBindTexture(GL_TEXTURE_2D, gl->tex);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(float)*2, verts);
glTexCoordPointer(2, GL_FLOAT, sizeof(float)*2, tcoords);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(unsigned int), colors);
glDrawArrays(GL_TRIANGLES, 0, nverts);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
Here is the core profile render code:
static void gl3fons__renderDraw(void* userPtr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts)
{
GLFONScontext* gl = (GLFONScontext*)userPtr;
if (gl->tex == 0) return;
if (gl->shader == 0) return;
if (gl->vao == 0) return;
if (gl->vbo == 0) return;
// init shader
glUseProgram(gl->shader);
// init texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gl->tex);
glUniform1i(gl->texture_uniform, 0);
// init our projection matrix
glUniformMatrix4fv(gl->projMat_uniform, 1, false, gl->projMat);
// bind our vao
glBindVertexArray(gl->vao);
// setup our buffer
glBindBuffer(GL_ARRAY_BUFFER, gl->vbo);
glBufferData(GL_ARRAY_BUFFER, (2 * sizeof(float) * 2 * nverts) + (sizeof(int) * nverts), NULL, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 2 * nverts, verts);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 2 * nverts, sizeof(float) * 2 * nverts, tcoords);
glBufferSubData(GL_ARRAY_BUFFER, 2 * sizeof(float) * 2 * nverts, sizeof(int) * nverts, colors);
// setup our attributes
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void *) (sizeof(float) * 2 * nverts));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(int), (void *) (2 * sizeof(float) * 2 * nverts));
glDrawArrays(GL_TRIANGLES, 0, nverts);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
}
I made a small test for each implementation and the results show that immediate mode is significantly faster than core.
Both tests fill the screen with AAA... and I log the time it took to do this for each frame. This is the loop:
// Create GL stash for 512x512 texture, our coordinate system has zero at top-left.
struct FONScontext* fs = glfonsCreate(512, 512, FONS_ZERO_TOPLEFT);
// Add font to stash.
int fontNormal = fonsAddFont(fs, "sans", "fontstash/example/DroidSerif-Regular.ttf");
// Render some text
float dx = 10, dy = 10;
unsigned int white = glfonsRGBA(255,255,255,255);
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
fonsSetFont(fs, fontNormal);
fonsSetSize(fs, 20.0f);
fonsSetColor(fs, white);
for(int i = 0; i < 90; i++){
for( int j = 0; j < 190; j++){
dx += 10;
fonsDrawText(fs, dx, dy, "A", NULL);
}
dy += 10;
dx = 10;
}
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> time_span = t2 - t1;
std::cout<<"Time to render: "<<time_span.count()<<"ms"<<std::endl;
And the results show more than 400ms difference between the two:
Core profile (left) vs Immediate mode (right)
What should be changed in order to speed up performance?
I don't know exactly what gl is in this program, but it's pretty clear that, every time you want to render a piece of text, you perform the following operations:
Allocate storage for a buffer, reallocating whatever storage had been created from the last time this was called.
Perform three separate uploads of data to that buffer.
These are not good ways to stream vertex data to the GPU. There are specific techniques for doing this well, but this is not one of them. In particular, the fact that you are constantly reallocating the same buffer is going to kill performance.
The most effective way to deal with this is to have a single buffer with a fixed amount of storage. It gets allocated exactly once and never again. Ideally, whatever API you're getting vertex data from would provide it in an interleaved format, so that you would only need to perform one upload rather than three. But apparently, Fontstash is apparently not so generous.
In any case, the main idea is to avoid reallocation and synchronization. The latter means never trying to write over data that has been written to recently. So your buffer needs to be sufficiently large to hold twice the number of font vertices you ever expect to render. Essentially, you double-buffer the vertex data: writing to one set of data while the other set is being read from.
So at the beginning of the frame, you figure out what the byte offset to where you want to render will be. This will either be the start of the buffer or half-way through it. Then, for each blob of text, you write vertex data to this offset and increment the offset accordingly.
And to avoid having to change VAO state, you should interleave the vertex data manually. Instead of uploading three arrays, you should interleave the vertices so that you're effectively making one gigantic array of vertices. So you never need to call glVertexAttribPointer in the middle of this function; you just use the parameters to glDraw* to draw the part of the array you want.
This also means you only need one glBufferSubData call. But if you have access to persistent mapped buffers, you don't even need that, since you can just write to the memory directly while using the other portion of it. Though if you use persistent mapping, you will need to use a fence sync object when you switch buffer regions to make sure that you're not writing to vertex data that is still being read by the GPU.
I am having difficulty to draw vertices from a structure-of-array like data structure. I think it might be the way I am using the stride and pointer arguments in glVertexAttribPointer call. I have a structure like this:
struct RadarReturn_t
{
float32_t x;
float32_t y;
float32_t z;
float32_t prob;
}
And I am using RadarReturn_t in another struct like this:
struct Detections_t
{
uint32_t currentScanNum;
std::array<RadarReturn_t, 64> detections;
}
Lets assume I want to draw a 100 of Detections_t. I have created one VBO to pack all of this information like this:
glGenBuffers(1, &mRadarVbo);
for (uint32_t iScan = 0; iScan < mMaxNumScans; ++iScan)
{
// Set the timestamp to 0U
mPersistentDetections.at(iScan).currentScanNum = 0U;
for (uint32_t iDet = 0; iDet < 64; ++iDet)
{
RadarReturn_t& detection = mPersistentDetections.at(iScan).detections.at(iDet);
detection.x = 0.0F;
detection.y = 0.0F;
detection.z = 0.0F;
detection.probability = 0.0F;
}
}
// Bind the VBO and copy the initial data to the graphics card
glBindBuffer(GL_ARRAY_BUFFER, mRadarVbo);
glBufferData(GL_ARRAY_BUFFER,
mMaxNumScans * sizeof(DetectionData_t),
&mPersistentDetections,
GL_DYNAMIC_DRAW);
Where mPersistentDetections is:
std::array<DetectionData_t, mMaxNumScans> mPersistentDetections;
Later on in my code, I update the buffer with new incoming data like this for currentScanNum:
// Offset is: 64 Radar returns plus one scanNumber
uint32_t offset = scanNum* ((64*sizeof(RadarReturn_t)) + 1*sizeof(GLuint));
glBufferSubData(GL_ARRAY_BUFFER, offset , sizeof(GLuint), &mPersistentDetections.at(scanNum).currentScanNum)
and like this for detections:
uint32_t dataSize = 64 * sizeof(RadarReturn_t);
glBufferSubData(GL_ARRAY_BUFFER,
offset + sizeof(GLuint),
dataSize,
&mPersistentDetections.at(scanNum).detections);
This is how I represent the VAO:
// Bind the VAO
glBindVertexArray(mRadarVao);
// Specify the layout of timestamp data
glVertexAttribIPointer(0,
1,
GL_UNSIGNED_INT,
sizeof(DetectionData_t),
(GLvoid*) 0);
// Specify the layout of the radar return data
glVertexAttribPointer(1,
4,
GL_FLOAT,
GL_FALSE,
sizeof(DetectionData_t),
(GLvoid*) (sizeof(GLuint)));
And finally the draw call:
glDrawArrays(GL_POINTS, 0, mMaxNumScans* 64);
If I am drawing this for mMaxNumScans = 100 am not able to draw 100x64 vertices here for some reason. Can you please point me out where I am going wrong?
EDIT:
As per suggestion from #Rabbid76, I have modified the Detections_t struct as follows:
struct Detections_t
{
std::array<RadarReturn_t, 64> detections;
std::array<uint32_t, 64> scanNumbers;
}
I have also appropriately modified glBufferData and glBufferSubData calls. And here is where I am still having an issue. I am not able to get the correct stride argument.
glVertexAttribPointer(0,
mDetectionAttributeSize,
GL_FLOAT,
GL_FALSE,
sizeof(RadarReturn_t),
reinterpret_cast<void*>offsetof(DetectionData_t, detectionData_t::detections)));
glVertexAttribIPointer(1,
mTimestampAttributeSize,
GL_INT,
sizeof(RadarReturn_t),
reinterpret_cast<void*>(offsetof(DetectionData_t, DetectionData_t::scanCounter)));
If I set the stride of attribute 0 to sizeof(Detection_t), not all the points will be drawn. Only sizeof(RadarReturn_t) draws all the points.
And if I set the stride of attribute 1 to sizeof(Detection_t) the color (I am using the scanNumber to vary the alpha value) of the detections becomes transparent after only a few scans.
I would appreciate if someone can tell me what the stride value is supposed to be in this case for attribute 0 and attribute 1.
I am going to answer my question. I was able to get it working properly by changing the RadarReturn_t struct like this:
struct RadarReturn_t
{
float32_t x;
float32_t y;
float32_t z;
float32_t prob;
int32_t scanCounter;
};
And then using the attributes like this:
glVertexAttribPointer(0,
4,
GL_FLOAT,
GL_FALSE,
sizeof(RadarReturn_t),
nullptr));
glVertexAttribIPointer(1,
1,
GL_INT,
sizeof(RadarReturn_t),
(void*) (4 * sizeof(GL_FLOAT)));
I am studying the source code of an open source project and they have a use of the function glDrawElements which I don't understand. While being a programmer, I am quite new to the GL API so would appreciate if someone could tell me how this works.
Let's start with the drawing part. The code looks like this:
for (int i = 0; i < numObjs; i++) {
glDrawElements(GL_TRIANGLES, vboIndexSize(i), GL_UNSIGNED_INT, (void*)(UPTR)vboIndexOffset(i));
}
vboIndiexSize(i) returns the number of indices for the current object, and vboIndexOffset returns the offset in bytes, in a flat memory array in which vertex data AND the indices of the objects are stored.
The part I don't understand, is the (void*)(UPTR)vboIndexOffset(i)). I look at the code many times and the function vboIndexOffset returns a int32 and UPTR also cast the returned value to an int32. So how you can you cast a int32 to a void* and expect this to work? But let's assume I made a mistake there and that it actually returns a pointer to this variable instead. The 4th argument of the glDrawElements call is an offset in byte within a memory block. Here is how the data is actually stored on the GPU:
int ofs = m_vertices.getSize();
for (int i = 0; i < numObj; i++)
{
obj[i].ofsInVBO = ofs;
obj[i].sizeInVBO = obj[i].indices->getSize() * 3;
ofs += obj[i].indices->getNumBytes();
}
vbo.resizeDiscard(ofs);
memcpy(vbo.getMutablePtr(), vertices.getPtr(), vertices.getSize());
for (int i = 0; i < numObj; i++)
{
memcpy(
m_vbo.getMutablePtr(obj[i].ofsInVBO),
obj[i].indices->getPtr(),
obj[i].indices->getNumBytes());
}
So all they do is calculate the number of bytes needed to store the vertex data then add to this number the number of bytes needed to store the indices of all the objects we want to draw. Then they allocate memory of that size, and copy the data in this memory: first the vertex data and then the indices. One this is done they push it to the GPU using:
glGenBuffers(1, &glBuffer);
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
checkSize(size, sizeof(GLsizeiptr) * 8 - 1, "glBufferData");
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, data, GL_STATIC_DRAW);
What's interesting is that they store everything in the GL_ARRAY_BUFFER. They never store the vertex data in a GL_ARRAY_BUFFER and then the indices using a GL_ELEMENT_ARRAY_BUFFER.
But to go back to the code where the drawing is done, they first do the usual stuff to declare vertex attribute. For each attribute:
glBindBuffer(GL_ARRAY_BUFFER, glBuffer);
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, size, type, GL_FALSE, stride, pointer);
This makes sense and is just standard. And then the code I already mentioned:
for (int i = 0; i < numObjs; i++) {
glDrawElements(GL_TRIANGLES, vboIndexSize(i), GL_UNSIGNED_INT, (void*)(UPTR)vboIndexOffset(i));
}
So the question: even if (UPTR) actually returns the pointer to variable (the code doesn't indicate this but I may be mistaken, it's a large project), I didn't know it was possible to store all vertex and indices data with the same memory block using GL_ARRAY_BUFFER and then using glDrawElements and having the 4th argument being the offset to the first element of this index list for the current object from this memory block. I thought you needed to use GL_ARRAY_BUFFER and GL_ELEMENT_BUFFER to declare the vertex data and the indices separately. I didn't think you could declare all the data in one go using GL_ARRAY_BUFFER and can't get it to work on my side anyway.
Has anyone see this working before? I haven't got a chance to get it working as yet, and wonder if someone could just potentially tell me if there's something specific I need to be aware of to get it to work. I tested with a simple triangle with position, normal and texture coordinates data, thus I have 8 * 3 floats for the vertex data and I have an array of 3 integers for the indices, 0, 1, 2. I then copy everything in a memory block, initialize the glBufferData with this, then try to draw the triangle with:
int n = 96; // offset in bytes into the memory block, fist int in the index list
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, (void*)(&n));
It doesn't crash but I can't see the triangle.
EDIT:
Adding the code that doesn't seem to work for me (crashes).
float vertices[] = {
0, 1, 0, // Vertex 1 (X, Y)
2, -1, 0, // Vertex 2 (X, Y)
-1, -1, 0, // Vertex 3 (X, Y)
3, 1, 0,
};
U8 *ptr = (U8*)malloc(4 * 3 * sizeof(float) + 6 * sizeof(unsigned int));
memcpy(ptr, vertices, 4 * 3 * sizeof(float));
unsigned int indices[6] = { 0, 1, 2, 0, 3, 1 };
memcpy(ptr + 4 * 3 * sizeof(float), indices, 6 * sizeof(unsigned int));
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(float) + 6 * sizeof(unsigned int), ptr, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
free(ptr);
Then when it comes to draw:
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// see stackoverflow.com/questions/8283714/what-is-the-result-of-null-int/
typedef void (*TFPTR_DrawElements)(GLenum, GLsizei, GLenum, uintptr_t);
TFPTR_DrawElements myGlDrawElements = (TFPTR_DrawElements)glDrawElements;
myGlDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, uintptr_t(4 * 3 * sizeof(float)));
This crashes the app.
see answer below for solution
This is due to OpenGL re-using fixed-function pipeline calls. When you bind a GL_ARRAY_BUFFER VBO, a subsequent call to glVertexAttribPointer expects an offset into the VBO (in bytes), which is then cast to a (void *). The GL_ARRAY_BUFFER binding remains in effect until another buffer is bound, just as the GL_ELEMENT_ARRAY_BUFFER binding remains in effect until another 'index' buffer is bound.
You can encapsulate the buffer binding and attribute pointer (offset) states using a Vertex Array Object.
The address in your example isn't valid. Cast offsets with: (void *) n
Thanks for the answers. I think though that (and after doing some research on the web),
first you should be using glGenVertexArray. It seems that this is THE standard now for OpenGL4.x so rather than calling glVertexAttribPointer before drawing the geometry, it seems like it's best practice to create a VAO when the data is pushed to the GPU buffers.
I (actually) was able to make combine the vertex data and the indices within the SAME buffer (a GL_ARRAY_BUFFER) and then draw the primitive using glDrawElements (see below). The standard way anyway is to push the vertex data to a GL_ARRAY_BUFFER and the indices to a GL_ELEMENT_ARRAY_BUFFER separately. So if that's the standard way of doing it, it's probably better not to try to be too smart and just use these functions.
Example:
glGenBuffers(1, &vbo);
// push the data using GL_ARRAY_BUFFER
glGenBuffers(1, &vio);
// push the indices using GL_ELEMENT_ARRAY_BUFFER
...
glGenVertexArrays(1, &vao);
// do calls to glVertexAttribPointer
...
Please correct me if I am wrong, but that seems the correct (and only) way to go.
EDIT:
However, it is actually possible to "pack" the vertex data and the indices together into an ARRAY_BUFFER as long as a call to glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo) is done prior to calling glDrawElements.
Working code (compared with code in original post):
float vertices[] = {
0, 1, 0, // Vertex 1 (X, Y)
2, -1, 0, // Vertex 2 (X, Y)
-1, -1, 0, // Vertex 3 (X, Y)
3, 1, 0,
};
U8 *ptr = (U8*)malloc(4 * 3 * sizeof(float) + 6 * sizeof(unsigned int));
memcpy(ptr, vertices, 4 * 3 * sizeof(float));
unsigned int indices[6] = { 0, 1, 2, 0, 3, 1 };
memcpy(ptr + 4 * 3 * sizeof(float), indices, 6 * sizeof(unsigned int));
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(float) + 6 * sizeof(unsigned int), ptr, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);
free(ptr);
Then when it comes to draw:
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo); // << THIS IS ACTUALLY NOT NECESSARY
// VVVV THIS WILL MAKE IT WORK VVVV
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// see stackoverflow.com/questions/8283714/what-is-the-result-of-null-int/
typedef void (*TFPTR_DrawElements)(GLenum, GLsizei, GLenum, uintptr_t);
TFPTR_DrawElements myGlDrawElements = (TFPTR_DrawElements)glDrawElements;
myGlDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, uintptr_t(4 * 3 * sizeof(float)));
I have trouble rendering some geometry by using a vertex buffer object. I intend to draw a plane of points, so basically one vertex at every discrete position in my space. However, I cannot render that plane, as every time I call glDrawElements(...), application crashes returning an access violation exception. There must be some mistake while initialization, I guess.
This is what I have so far:
#define SPACE_X 512
#define SPACE_Z 512
typedef struct{
GLfloat x, y, z; // position
GLfloat nx, ny, nz; // normals
GLfloat r, g, b, a; // colors
} Vertex;
typedef struct{
GLuint i; // index
} Index;
// create vertex buffer
GLuint vertexBufferObject;
glGenBuffers(1, &vertexBufferObject);
// create index buffer
GLuint indexBufferObject;
glGenBuffers(1, &indexBufferObject);
// determine number of vertices / primitives
const int numberOfVertices = SPACE_X * SPACE_Z;
const int numberOfPrimitives = numberOfVertices; // As I'm going to render GL_POINTS, number of primitives is the same as number of vertices
// create vertex array
Vertex* vertexArray = new Vertex[numberOfVertices];
// create index array
Index* indexArray = new Index[numberOfPrimitives];
// create planes (vertex array)
// color of the vertices is red for now
int index = -1;
for(GLfloat x = -SPACE_X / 2; x < SPACE_X / 2; x++) {
for(GLfloat z = -SPACE_Z / 2; z < SPACE_Z / 2; z++) {
index++;
vertexArray[index].x = x;
vertexArray[index].y = 0.0f;
vertexArray[index].z = z;
vertexArray[index].nx = 0.0f;
vertexArray[index].ny = 0.0f;
vertexArray[index].nz = 1.0f;
vertexArray[index].r = 1.0;
vertexArray[index].g = 0.0;
vertexArray[index].b = 0.0;
vertexArray[index].a = 1.0;
}
}
// bind vertex buffer
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
// buffer vertex array
glBufferData(GL_ARRAY_BUFFER, numberOfVertices * sizeof(Vertex), vertexArray, GL_DTREAM_DRAW);
// bind vertex buffer again
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
// enable attrib index 0 (positions)
glEnableVertexAttribArray(0);
// pass positions in
glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), vertexArray);
// enable attribute index 1 (normals)
glEnableVertexAttribArray(1);
// pass normals in
glVertexAttribPointer((GLuint)1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertexArray[0].nx);
// enable attribute index 2 (colors)
glEnableVertexAttribArray(2);
// pass colors in
glVertexAttribPointer((GLuint)2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertexArray[0].r);
// create index array
for(GLunit i = 0; i < numberOfPrimitives; i++) {
indexArray[i].i = i;
}
// bind buffer
glBindBuffer(GL_ELEMENET_ARRAY_BUFFER, indexBufferObject);
// buffer indices
glBufferData(GL_ELEMENET_ARRAY_BUFFER, numberOfPrimitives * sizeof(Index), indexArray, GL_STREAM_DRAW);
// bind buffer again
glBindBuffer(GL_ELEMENET_ARRAY_BUFFER, indexBufferObject);
// AND HERE IT CRASHES!
// draw plane of GL_POINTS
glDrawElements(GL_POINTS, numberOfPrimitives, GL_UNSIGNED_INT, indexArray);
// bind default buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// delete vertex / index buffers
glDeleteBuffers(1, &vertexBufferObject);
glDeleteBuffers(1, &indexBufferObject);
delete[] vertexArray;
vertexArray = NULL;
delete[] indexArray;
indexArray = NULL;
When you are using buffer objects, the last parameters in the gl*Pointer and 4th parameter in glDrawElements are no longer addresses in main memory (yours still are!), but offsets into the buffer objects. Make sure to compute these offsets in bytes! The "offsetof" macro is very helpful there.
Look at the second example on this page and compare it to what you did: http://www.opengl.org/wiki/VBO_-_just_examples
And you have one typo: GL_DTREAM_DRAW.
The method glEnableClientState(...) is deprecated! Sorry, for some reason I had overseen that fact.