2

我是 OpenGL 的新手,我一直在使用红皮书和超级圣经。在 SB 中,我已经了解了有关使用从文件加载的对象的部分。到目前为止,我认为我理解正在发生的事情以及如何做没有问题,但这让我开始考虑在我自己的应用程序中制作我自己的网格 - 本质上是一个建模应用程序。我已经通过我的参考资料和互联网进行了大量搜索,但我还没有找到一个很好的教程来将这些功能实现到自己的应用程序中。我找到了一个仅提供此功能的API,但我正在尝试了解实现;不仅仅是界面。

到目前为止,我已经创建了一个“应用程序”(我轻描淡写地使用了这个术语),它为您提供了一个可以单击并添加顶点的视图。顶点不连接,只是显示在您单击的位置。我担心的是,我在试验时偶然发现的这种方法不是我应该实施这个过程的方式。

我正在使用 Mac 并在 Xcode 中使用 Objective-C 和 C。

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

该应用程序应该获取鼠标单击的位置并提供它是一个顶点位置。对于每个添加的顶点,我首先绑定 VBO 以确保它处于活动状态。接下来,我创建一个新数组来保存我当前的顶点位置(totalVertices)加上一个顶点的空间(x 和 y 为 + 2)。然后我使用 glGetBufferSubData 从 VBO 取回数据并将其放入这个数组中。使用 for 循环,我将 X 和 Y 数字添加到数组的末尾。最后,我将这些数据发送回 GPU 到 VBO 并调用 totalVertices++,这样我就知道下次我想添加一个顶点时数组中有多少个顶点。

这让我想到了我的问题:我这样做对吗?换句话说,我是否应该在 CPU 端保留 BufferData 的副本,这样我就不必调用 GPU 并将数据发送回进行编辑?这样,我不会调用 glGetBufferSubData,我会创建一个更大的数组,将新顶点添加到末尾,然后调用 glBufferData 以使用更新的顶点数据重新分配 VBO。

** 我试图包含我的思考过程,以便像我这样在编程方面非常缺乏经验的人可以希望能够理解我想要做什么。我不希望任何人因为我对我所做的解释而感到生气。**

4

1 回答 1

3

我当然会避免读回数据。不仅是因为额外的数据拷贝,也是为了避免 CPU 和 GPU 之间的同步。

当您进行 OpenGL 调用时,您可以想象驱动程序构建一个 GPU 命令,将其排队等待稍后提交给 GPU,然后返回。这些命令将在稍后提交给 GPU。这个想法是 GPU 可以尽可能独立于 CPU 上运行的任何东西(包括您的应用程序)运行。CPU 和 GPU 以最小的依赖性并行运行对于性能来说是非常可取的。

对于大多数glGet*()调用,这种异步执行模型会失效。他们通常必须等到 GPU 完成所有(或至少一些)待处理命令才能返回数据。因此,只有 GPU 运行时 CPU 可能会阻塞,这是不可取的。

出于这个原因,您绝对应该保留数据的 CPU 副本,这样您就不必再读回它了。

除此之外,还有一些选择。这完全取决于您的使用模式、特定平台的性能特征等。要真正充分利用它,没有办法实现多种变体并对其进行基准测试。

对于您所描述的内容,我可能会从类似于std::vectorC++ 中的 a 的东西开始。您分配的内存量(通常命名为容量)大于您目前所需的内存量。然后您可以在不重新分配的情况下添加数据,直到您填满分配的容量。此时,您可以将容量翻倍。

glBufferData()将此应用于 OpenGL,您可以通过使用 NULL 作为数据指针调用来保留一定数量的内存。跟踪您分配的容量,并通过调用来填充缓冲区glBufferSubData()。在示例代码中添加单个点时,您将glBufferSubData()只使用新点进行调用。只有当你的容量用完时,你才会调用glBufferData()一个新的容量,然后用你已经拥有的所有数据填充它。

在伪代码中,初始化看起来像这样:

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

然后每次添加一个点:

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]);
}

作为 , 的替代方法glBufferSubData()glMapBufferRange()是更新缓冲区数据时要考虑的另一种选择。更进一步,您可以考虑使用多个缓冲区,并在它们之间循环,而不是只更新单个缓冲区。这就是基准测试发挥作用的地方,因为没有一种方法可以最适合每个可能的平台和用例。

于 2014-08-13T07:14:58.330 回答