objective-ccmacosopenglvertexdata

How does one add vertices to a mesh object in OpenGL?


I am new to OpenGL and I have been using The Red Book, and the Super Bible. In the SB, I have gotten to the section about using objects loaded from files. So far, I don't think I have a problem understanding what is going on and how to do it, but it got me thinking about making my own mesh within my own app--in essence, a modeling app. I have done a lot of searching through both of my references as well as the internet, and I have yet to find a nice tutorial about implementing such functionality into one's own App. I found an API that just provides this functionality, but I am trying to understand the implementation; not just the interface.

Thus far, I have created an "app" (I use this term lightly), that gives you a view that you can click in and add vertices. The vertices don't connect, just are just displayed where you click. My concern is that this method I stumbled upon while experimenting is not the way I should be implementing this process.

I am working on a Mac and using Objective-C and C in Xcode.

MyOpenGLView.m #import "MyOpenGLView.h"

@interface MyOpenGLView () {

NSTimer *_renderTimer
Gluint VAO, VBO;

GLuint totalVertices;
GLsizei bufferSize;

}

@end

@implementation MyOpenGLView

/* Set up OpenGL view with a context and pixelFormat with doubleBuffering */

/* NSTimer implementation */

- (void)drawS3DView {

    currentTime = CACurrentMediaTime();

    NSOpenGLContext *currentContext = self.openGLContext;
    [currentContext makeCurrentContext];
    CGLLockContext([currentContext CGLContextObj]);

    const GLfloat color[] = {
        sinf(currentTime * 0.2),
        sinf(currentTime * 0.3),
        cosf(currentTime * 0.4),
        1.0
    };

    glClearBufferfv(GL_COLOR, 0, color);

    glUseProgram(shaderProgram);

    glBindVertexArray(VAO);

    glPointSize(10);
    glDrawArrays(GL_POINTS, 0, totalVertices);

    CGLFlushDrawable([currentContext CGLContextObj]);
    CGLUnlockContext([currentContext CGLContextObj]);

}

#pragma mark - User Interaction

- (void)mouseUp:(NSEvent *)theEvent {

    NSPoint mouseLocation = [theEvent locationInWindow];
    NSPoint mouseLocationInView = [self convertPoint:mouseLocation fromView:self];

    GLfloat x = -1 + mouseLocationInView.x * 2/(GLfloat)self.bounds.size.width;
    GLfloat y = -1 + mouseLocationInView.y * 2/(GLfloat)self.bounds.size.height;

    NSOpenGLContext *currentContext = self.openGLContext;
    [currentContext makeCurrentContext];
    CGLLockContext([currentContext CGLContextObj]);

    [_renderer addVertexWithLocationX:x locationY:y];

    CGLUnlockContext([currentContext CGLContextObj]);

}

- (void)addVertexWithLocationX:(GLfloat)x locationY:(GLfloat)y {

    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    GLfloat vertices[(totalVertices * 2) + 2];

    glGetBufferSubData(GL_ARRAY_BUFFER, 0, (totalVertices * 2), vertices);

    for (int i = 0; i < ((totalVertices * 2) + 2); i++) {
        if (i == (totalVertices * 2)) {
            vertices[i] = x;
        } else if (i == (totalVertices * 2) + 1) {
            vertices[i] = y;
        }
    }

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    totalVertices ++;

}

@end

The app is supposed take the location of the mouse click and provide it is a vertex location. With each added vertex, I first bind the VBO to make sure it is active. Next, I create a new array to hold my current vertex location's (totalVertices) plus space for one more vertex (+ 2 for x and y). Then I used glGetBufferSubData to bring the data back from the VBO and put it into this array. Using a for loop I add the X and Y numbers to the end of the array. Finally, I send this data back to the GPU into a VBO and call totalVertices++ so I know how many vertices I have in the array next time I want to add a vertex.

This brings me to my question: Am I doing this right? Put another way, should I be keeping a copy of the BufferData on the CPU side so that I don't have to call out to the GPU and have the data sent back for editing? In that way, I wouldn't call glGetBufferSubData, I would just create a bigger array, add the new vertex to the end, and then call glBufferData to realloc the VBO with the updated vertex data.

** I tried to include my thinking process so that someone like myself who is very inexperienced in programming can hopefully understand what I am trying to do. I don't want anyone to be offended by my explanations of what I did. **


Solution

  • I would certainly avoid reading the data back. Not only because of the extra data copy, but also to avoid synchronization between CPU and GPU.

    When you make an OpenGL call, you can picture the driver building a GPU command, queuing it up for later submission to the GPU, and then returning. These commands will then be submitted to the GPU at a later point. The idea is that the GPU can run as independently as possible from whatever runs on the CPU, which includes your application. CPU and GPU operating in parallel with minimal dependencies is very desirable for performance.

    For most glGet*() calls, this asynchronous execution model breaks down. They will often have to wait until the GPU completed all (or at least some) pending commands before they can return the data. So the CPU might block while only the GPU is running, which is undesirable.

    For that reason, you should definitely keep your CPU copy of the data so that you don't ever have to read it back.

    Beyond that, there are a few options. It will all depend on your usage pattern, the performance characteristics of the specific platform, etc. To really get the maximum out of it, there's no way around implementing multiple variations, and benchmarking them.

    For what you're describing, I would probably start with something that works similar to a std::vector in C++. You allocate a certain amount of memory (typically named capacity) that is larger than what you need at the moment. Then you can add data without reallocating, until you fill the allocated capacity. At that point, you can for example double the capacity.

    Applying this to OpenGL, you can reserve a certain amount of memory by calling glBufferData() with NULL as the data pointer. Keep track of the capacity you allocated, and populate the buffer with calls to glBufferSubData(). When adding a single point in your example code, you would call glBufferSubData() with just the new point. Only when you run out of capacity, you call glBufferData() with a new capacity, and then fill it with all the data you already have.

    In pseudo-code, the initialization would looks something like this:

    int capacity = 10;
    glBufferData(GL_ARRAY_BUFFER,
        capacity * sizeof(Point), NULL, GL_DYNAMIC_DRAW);
    std::vector<Point> data;
    

    Then each time you add a point:

    data.push_back(newPoint);
    if (data.size() <= capacity) {
        glBufferSubData(GL_ARRAY_BUFFER,
            (data.size() - 1) * sizeof(Point), sizeof(Point), &newPoint);
    } else {
        capacity *= 2;
        glBufferData(GL_ARRAY_BUFFER,
            capacity * sizeof(Point), NULL, GL_DYNAMIC_DRAW);
        glBufferSubData(GL_ARRAY_BUFFER,
            0, data.size() * sizeof(Point), &data[0]);
    }
    

    As an alternative to glBufferSubData(), glMapBufferRange() is another option to consider for updating buffer data. Going farther, you can look into using multiple buffers, and cycle through them, instead of updating just a single buffer. This is where benchmarking comes into play, because there isn't a single approach that will be best for every possible platform and use case.