【问题标题】:Why is OpenGL immediate mode faster than core?为什么 OpenGL 立即模式比核心模式快?
【发布时间】:2020-11-26 01:00:13
【问题描述】:

我正在使用以下库在 OpenGL 中呈现文本:fontstash。我还有另一个header file,它增加了对 OpenGL 3.0+ 的支持。问题是为什么核心配置文件的渲染实现比立即模式慢很多

这是立即模式的渲染代码:

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

这里是核心配置文件渲染代码:

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

我为每个实现做了一个小测试,结果表明即时模式比核心模式明显快。

两个测试都用AAA... 填充屏幕,我记录了每一帧执行此操作所花费的时间。这是循环:

// 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;

结果显示两者相差超过400ms: Core profile (left) vs Immediate mode (right)

为了提高性能应该改变什么?

【问题讨论】:

  • 我认为这是因为您每次都设置了缓冲区。你应该只做一次,因此当你真正想要渲染它时使用更少的指令。
  • 如果您在谈论glBufferData,我尝试将代码更改为仅分配一次,然后使用glBufferSubData 仅替换数据。由于某种原因,它大大变得更糟,因为渲染时间上升到 8000+ms。我的猜测是它在后台处理多线程,但我不确定。
  • 缓冲区上传很昂贵。仅渲染 VAO 不是。假设你的文本在每一帧中都没有改变(这对于 60 FPS 或更高的人来说很难阅读),你应该重写你的代码,它只调用一次 glBufferData()(当文本改变时)。

标签: c++ opengl


【解决方案1】:

我不知道这个程序中的gl到底是什么,但是很清楚,每次你想渲染一段文本时,你都要执行以下操作:

  1. 为缓冲区分配存储空间,重新分配自上次调用该缓冲区以来创建的任何存储空间。
  2. 向该缓冲区执行三个单独的数据上传

这些不是将顶点数据流式传输到 GPU 的好方法。有specific techniques for doing this well,但这不是其中之一。特别是,您不断重新分配同一个缓冲区的事实会影响性能。

解决这个问题的最有效方法是使用具有固定存储量的单个缓冲区。它只被分配一次,永远不会再分配。理想情况下,无论您从中获取顶点数据的任何 API 都将以交错格式提供,因此您只需要执行一次上传而不是三次。但显然,Fontstash 显然没有那么大方。

无论如何,主要思想是避免重新分配和同步。后者意味着永远不要尝试覆盖最近写入的数据。因此,您的缓冲区需要足够大,以容纳 两倍 您期望渲染的字体顶点数。本质上,您对顶点数据进行双重缓冲:写入一组数据,同时读取另一组数据。

因此,在帧的开始,您要确定要渲染的位置的字节偏移量是多少。这将是缓冲区的开始或中途。然后,对于每个文本块,将顶点数据写入此偏移量并相应地增加偏移量。

为了避免不得不更改 VAO 状态,您应该手动交错顶点数据。您应该交错顶点而不是上传三个数组,以便有效地制作一个巨大的顶点数组。所以你永远不需要在这个函数中间调用glVertexAttribPointer;你只需使用glDraw*的参数来绘制你想要的数组部分。

这也意味着您只需要打一个glBufferSubData 电话。但是,如果您可以访问持久映射缓冲区,您甚至不需要它,因为您可以在使用内存的其他部分时直接写入内存。虽然如果您使用持久映射,则在切换缓冲区时需要使用fence sync object,以确保您不会写入仍在由 GPU 读取的顶点数据。

【讨论】:

  • 非常感谢您的详细解释!我会研究你的建议。我已经尝试在开始时将glBufferData 调用更改为仅一次,但性能从 400 毫秒变为 8000 + 毫秒(我的猜测是后台多线程,因此其他线程在第一个线程之后没有分配的缓冲区,但我不确定。)我会尝试实施您的建议,并希望它加快速度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-24
  • 2017-12-14
  • 2011-08-23
  • 2019-07-07
  • 1970-01-01
相关资源
最近更新 更多