【问题标题】:Curious slowdown in opengl when using instanced rendering使用实例渲染时opengl的奇怪减速
【发布时间】:2015-04-06 04:35:58
【问题描述】:

我遇到了一个非常奇怪的性能问题。到目前为止,我已将问题简化为:我正在使用glDrawElementsInstanced 在网格中渲染 20x20x20 立方体,只要我的相机远离原点,它就可以正常工作,但是当它靠近原点时,它开始停止。

我通过以下方式定义我的模型视图投影矩阵:

float distance=3.8;
Projection = glm::perspective(65.0f, (float)(width)/height, 0.1f, 300.0f);
View  = glm::lookAt(    glm::vec3(0,0,-distance),
                        glm::vec3(0,0,10),
                        glm::vec3(0,1,0));
Model = glm::rotate(glm::mat4(1.0f), 0.0f, glm::vec3(0.25f, 1.0f,0.75f));

距离为 40 时没有问题,但当距离减小到 3.8 或更低时,一切都会停止。

实际调用渲染是通过:

glBindVertexArray(cubeVAO);
glDrawElementsInstanced(GL_TRIANGLES, indices.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());

将所有顶点放在一个缓冲区中并通过调用进行渲染:

glBindVertexArray(nonInstancedVAO);
glDrawArrays(GL_TRIANGLES, 0,vertices.size() );

完全删除该行为。任何经历过类似行为的人都可以为我指明解决方案的方向吗?如果失败了,有谁知道如何追踪这样的事情?我希望我能够使用 gDEBugger 确定导致速度下降的原因,但这只是再次确认没有任何其他 opengl 调用,并且并不能真正帮助找出占用所有处理时间的原因。

另一个注意事项是 glDrawArraysInstanced 也显示出同样的减速,并且将调用分成 4 个单独的调用,每个调用有四分之一的几何图形也可以阻止减速。

更新

这是对问题的最小说明。

//Minimal reproduction of problem

#include <stdio.h>
#include <string>
#include <fstream>
#include <stdlib.h>
#include <string.h>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

// Include GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <vector>
#include <iostream>
#include <stdio.h>

//Set to true to use instanced rendering (glDrawElementsInstanced), false to render a generated grid instead (glDrawElements)
    #define Instanced true

//Translation from origin. Problme is pressent at 0 distance, but disapears at ex. 40.
    const float distanceFromOrigin=0;

// Function to load shaders
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path);


    int main(){

    int     width, height;
    bool    running = true;

    // Initialise GLFW
    glfwInit();

    glfwWindowHint(GLFW_SAMPLES,1);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT,GL_TRUE);
    glfwWindowHint(GLFW_VERSION_MAJOR, 4);

    GLFWwindow* windowRef = glfwCreateWindow( 512, 512, "",0,0);
    glfwMakeContextCurrent(windowRef);

    glewInit();

    //Load Shader
    GLuint programID = LoadShaders( "Simple.vs.c", "Simple.fs.c" );
    GLuint MatrixID = glGetUniformLocation(programID, "MVP");
    glUseProgram(programID);

    glm::mat4 Model,Projection,MVP,View,checkMVP;

    std::vector<GLuint>  sqIndice = {3,2,1,1,0,3,4,5,6,6,7,4,0,4,7,7,3,0,0,1,5,5,4,0,2,3,7,7,6,2,6,5,1,1,2,6,0,4,7,7,3,0};
    std::vector<GLfloat> sqVertex = {-1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1};
    std::vector<GLfloat> sqColor = {0.2472,0.24,0.6,0.6,0.24,0.442893,0.6,0.547014,0.24,0.24,0.6,0.33692,0.24,0.353173,0.6,0.6,0.24,0.563266,0.6,0.426641,0.24,0.263452,0.6,0.24};

    const float lattice = 5;
    const int mxn = 10;
    std::vector<GLfloat> v1 = {lattice,-1,0};
    std::vector<GLfloat> v2 = {1,lattice,0};
    std::vector<GLfloat> v3 = {0,0,lattice};
    std::vector<GLfloat> offset = {0,0,-distanceFromOrigin};

    std::vector<GLfloat> latticePoints,sqVertexGrid,sqColorGrid;// = {0,0,0};
    std::vector<GLuint> sqIndiceGrid;
// Looping stuff to generate the full grid of "instances" to render in a single call. 
    int instanceCount=0;
//Generate Lattice vectors, aswell as a vector containing the full grids of indices,vertexes and colors
    for(int x=-mxn;x<mxn;++x){
        for(int y=-mxn;y<mxn;++y){
            for(int z=-mxn;z<mxn;++z){
                for(int n=0;n<3;++n){
                    latticePoints.push_back( x*v1[n]+y*v2[n]+z*v3[n]+offset[n] );
                };
                for(int elm=0;elm<sqVertex.size();elm+=3){
                    for(int n=0;n<3;++n){
                        sqVertexGrid.push_back(sqVertex[elm+n]+x*v1[n]+y*v2[n]+z*v3[n]+offset[n]);
                        sqColorGrid.push_back(sqColor[elm+n]);
                    };
                };
                for(int elm=0;elm<sqIndice.size();++elm){
                    sqIndiceGrid.push_back(sqIndice[elm]+instanceCount*sqVertex.size()/3);
                };
                ++instanceCount;glewInit

            };
        };
    };

#if Instanced==true
//Initialize and fill vertex,color and indice buffers with the relevant data.
GLuint cubeVAO;
    glGenVertexArrays(1, &cubeVAO);
    glBindVertexArray(cubeVAO);
    glEnable(GL_DEPTH_TEST);

//Vertex buffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sqVertex.size()*sizeof(GLfloat), &sqVertex[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

//Color buffer
    GLuint colorBuffer;
    glGenBuffers(1, &colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, sqColor.size()*sizeof(GLfloat), &sqColor[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);

// Indice buffer
    GLuint indicesBuffer;
    glGenBuffers(1, &indicesBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sqIndice.size()*sizeof(GLuint), &sqIndice[0], GL_STATIC_DRAW);

//Lattice point buffer
    GLuint latticePointBuffer;
    glGenBuffers(1, &latticePointBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, latticePointBuffer);
    glBufferData(GL_ARRAY_BUFFER, latticePoints.size()*sizeof(GLfloat), &latticePoints[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,(void*)0);
    glVertexAttribDivisor(2,1);

glBindVertexArray(0);
#elif Instanced==false
GLuint cubeGridVAO;
    glGenVertexArrays(1, &cubeGridVAO);
    glBindVertexArray(cubeGridVAO);
    glEnable(GL_DEPTH_TEST);

//Vertex buffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sqVertexGrid.size()*sizeof(GLfloat), &sqVertexGrid[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

//Color buffer
    GLuint colorBuffer;
    glGenBuffers(1, &colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, sqColorGrid.size()*sizeof(GLfloat), &sqColorGrid[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);

// Indice buffer
    GLuint indicesBuffer;
    glGenBuffers(1, &indicesBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sqIndiceGrid.size()*sizeof(GLuint), &sqIndiceGrid[0], GL_STATIC_DRAW);

glBindVertexArray(0);
#endif


while(running)
{
        glfwGetFramebufferSize(windowRef, &width, &height);
        height = height > 0 ? height : 1;

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        Projection = glm::perspective(65.0f, (float)(width)/height, 0.1f, 300.0f);
        View  = glm::lookAt(    glm::vec3(0.0f,0.0f,-(distanceFromOrigin+3.8f)),
                                glm::vec3(0.0f,0.0f,100.0f),
                                glm::vec3(0.0f,1.0f,0.0f));
        Model = glm::rotate(glm::mat4(1.0f), 0.0f, glm::vec3(0.25f, 1.0f,0.75f));

        MVP = Projection*View*Model;
        glUniformMatrix4fv(MatrixID, 1, GL_FALSE,  glm::value_ptr(MVP));

        #if Instanced==true
            glBindVertexArray(cubeVAO);
            glDrawElementsInstanced(GL_TRIANGLES, sqIndice.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());
        #elif Instanced==false
            glBindVertexArray(cubeGridVAO);
            glDrawElements(GL_TRIANGLES, sqIndiceGrid.size(),GL_UNSIGNED_INT,(GLvoid*)(0));
        #endif

        glfwPollEvents();
        glfwSwapBuffers(windowRef);

        std::cout<<".\n";

    running = !glfwGetKey(windowRef,GLFW_KEY_ESCAPE) && !glfwWindowShouldClose(windowRef);
    }

    glfwDestroyWindow(windowRef);
    glfwTerminate();

    return 0;
};

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

        // Create the shaders
        GLuint VertexShaderID   = glCreateShader(GL_VERTEX_SHADER);
        GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

        // Read the Vertex Shader code from the file
        std::string VertexShaderCode;
        std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
        if(VertexShaderStream.is_open()){
                std::string Line = "";
                while(getline(VertexShaderStream, Line))
                        VertexShaderCode += "\n" + Line;
                VertexShaderStream.close();
        }else{
                printf("Impossible to open %s. Are you in the right directory?\n", vertex_file_path);
                return 0;
        }

        // Read the Fragment Shader code from the file
        std::string FragmentShaderCode;
        std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
        if(FragmentShaderStream.is_open()){
                std::string Line = "";
                while(getline(FragmentShaderStream, Line))
                        FragmentShaderCode += "\n" + Line;
                FragmentShaderStream.close();
        }

        GLint Result = GL_FALSE;
        int InfoLogLength;

        // Compile Vertex Shader
        printf("Compiling shader : %s\n", vertex_file_path);
        char const * VertexSourcePointer = VertexShaderCode.c_str();
        glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
        glCompileShader(VertexShaderID);

        // Check Vertex Shader
        glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
        glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
        if ( InfoLogLength > 0 ){
                std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
                glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
                printf("%s\n", &VertexShaderErrorMessage[0]);
        }

        // Compile Fragment Shader
        printf("Compiling shader : %s\n", fragment_file_path);
        char const * FragmentSourcePointer = FragmentShaderCode.c_str();
        glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
        glCompileShader(FragmentShaderID);

        // Check Fragment Shader
        glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
        glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
        if ( InfoLogLength > 0 ){
                std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
                glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
                printf("%s\n", &FragmentShaderErrorMessage[0]);
        }


        // Link the program
        printf("Linking program\n");
        GLuint ProgramID = glCreateProgram();
        glAttachShader(ProgramID, VertexShaderID);
        glAttachShader(ProgramID, FragmentShaderID);
        glLinkProgram(ProgramID);

        // Check the program
        glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
        glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
        if ( InfoLogLength > 0 ){
                std::vector<char> ProgramErrorMessage(InfoLogLength+1);
                glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
                printf("%s\n", &ProgramErrorMessage[0]);
        }

        glDeleteShader(VertexShaderID);
        glDeleteShader(FragmentShaderID);

        return ProgramID;
}

【问题讨论】:

  • 我已经在 580 GTX 上使用 100^3 (1M) 个立方体进行了实例化测试,运行速度为 30-50 fps,具体取决于相机位置。使用随机位置和紧凑立方体进行测试。没有注意到这里描述的任何减速。也许速度变慢只是由于过度绘制导致立方体覆盖了整个屏幕并从后向前绘制?
  • @JariKomppa 感谢您的评论,但问题不在于是否需要实例化。我正在以最简单的形式显示实例化导致问题的问题。
  • @JariKomppa 感谢您提供帮助的尝试,但您甚至看过代码吗?它有一个用于实例化的预处理器开关,并且仅在使用实例化时才会出现问题。建议我不使用实例化在任何方面都没有建设性。我不是在寻求一种渲染立方体的方法,而是在寻找我认为非常奇怪的行为的洞察力。
  • @jVincent 我很抱歉。彻头彻尾,你的问题毫无意义;实例化不应该与投影有任何关系,并且将绘图分成 4 个部分解决了它的事实表明,在驱动程序甚至硬件级别的某个地方存在某种奇怪的资源匮乏。你是否尝试过使用不同的 gfx 卡?我相当有信心这种行为因一种架构而异。另外,出于好奇,你有哪张卡?
  • @jVincent 尝试不同的驱动程序版本和不同的硬件。如果您的代码按照标准编码,并且仅在一个平台上显示奇怪的行为,则该平台有问题。通常 =)

标签: performance debugging opengl graphics


【解决方案1】:

好的,深呼吸,坐下:你的问题是显卡内存速度。

但您可以通过修复此错误来让 GPU 更轻松:

glDrawElementsInstanced(GL_TRIANGLES, sqIndice.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());

glDrawElementsInstanced 期望绘制的实例数作为最后一个参数。但是您传递了latticePoints 中的元素数量。这是实例数量的 3 倍。这会导致着色器内的零点(因为阻止了越界访问)。所以 16000 个立方体没有被平移,而是被绘制在同一个位置。这导致绘制立方体的正面 16000 次。深度缓冲区不会阻止这一点,因为面不会相互隐藏,它们在同一个位置。

因此,当您的distanceFromOrigin 减少时,16000 个中心立方体会变得越来越大。 OpenGL 必须绘制越来越多的像素。确切地说,还有很多。它必须绘制太多,以至于它达到了显卡内存的速度限制。

阅读Diagnose OpenGl Performance Problems了解整个故事。

【讨论】:

  • 嘿,我没有太多时间浏览这段旧代码,很抱歉我在接受和响应方面的缓慢。当遇到问题并且没有立即获得任何帮助时,我会按照我认为许多其他人的做法去做,然后继续解决这个问题。但是这里更大的问题不仅仅是如何让这个特定的代码正确,而是如何调试未知问题,你的博客文章就是一个非常棒的例子。非常感谢您的出色回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-19
  • 2014-09-02
  • 1970-01-01
相关资源
最近更新 更多