Проблема производительности с glDrawArraysInstanced

Я пытаюсь реализовать экземплярный алгоритм рисования OpenGL4, где каждый экземпляр состоит из одного треугольника. Основные причины, по которым я хочу реализовать этот вид алгоритма:

  • возможность использовать меньше памяти GPU в частом сценарии, когда цвета задаются для каждого треугольника, а не для каждой вершины
  • способность выполнять расчеты для каждого треугольника без использования геометрических шейдеров, которые, по моим экспериментам, резко замедляют весь конвейер

Моя программа рендеринга состоит из вершинного шейдера и фрагментного шейдера. Вершинный шейдер выглядит следующим образом:

#version 400 core

layout (location = 0) in vec3 tri_p0;
layout (location = 1) in vec3 tri_p1;
layout (location = 2) in vec3 tri_p2;
layout (location = 3) in vec4 tri_colorP0;
layout (location = 4) in vec4 tri_colorP1;
layout (location = 5) in vec4 tri_colorP2;

out FRAGMENT {
    vec4 color;
} vs_out;

uniform mat4 mvp_matrix;

void main(void) {
    vec3 position;
    vec4 color;

    if(gl_VertexID == 0) {
        position = tri_p0;
        color = tri_colorP0;
    }
    else if(gl_VertexID == 1) {
        position = tri_p1;
        color = tri_colorP1;
    }
    else if(gl_VertexID == 2) {
        position = tri_p2;
        color = tri_colorP2;
    }

    vs_out.color = color;

    gl_Position = mvp_matrix * vec4(position, 1.0);
}

Фрагментный шейдер вместо этого:

#version 400 core

layout (location = 0) out vec4 color;

in FRAGMENT {
    vec4 color;
} fs_in;

void main(void) {
    color = fs_in.color;
}

Как вы можете видеть, в моем вершинном шейдере я объявляю три атрибута вершины для позиций вершин и три атрибута вершины для цветов. Все эти атрибуты являются экземплярами, а их делитель установлен в 1.

Причина, по которой у меня есть три цветовых атрибута, заключается в том, что иногда я хочу иметь разные цвета для трех вершин треугольника, в то время как чаще у меня есть один цвет для всего треугольника. В этом последнем сценарии я просто присоединяю три цветовых атрибута к одному и тому же VBO, задавая один и тот же шаг и смещение.

Я написал тестовое приложение, которое рисует матрицу четырехугольников, каждый из которых состоит из двух треугольников. Это код, который я использовал для инициализации данных вершин:

int numQuadsPerRowCol = sqrtl(NUM_TRIANGLES / 2);
numTris = numQuadsPerRowCol * numQuadsPerRowCol * 2;

float stepX = (maxX - minX) / numQuadsPerRowCol;
float stepY = (maxY - minY) / numQuadsPerRowCol;

GLfloat* positions = new GLfloat[3 * 3 * numTris];
GLfloat* colors = new GLfloat[4 * numTris];

int k = 0;
int l = 0;

for (int i = 0; i < numQuadsPerRowCol; i++) {
    for (int j = 0; j < numQuadsPerRowCol; j++) {
        GLfloat color[4];

        int id = i * numQuadsPerRowCol + j;

        color[0] = ((id & 0x00ff0000) >> 16) / 255.0;
        color[1] = ((id & 0x0000ff00) >> 8) / 255.0;
        color[2] = (id & 0x000000ff) / 255.0;
        color[3] = 1.0;

        for (int t = 0; t < 2; t++) {
            for (int c = 0; c < 4; c++) {
                colors[l + c] = color[c];
            }
            l += 4;
        }

        GLfloat xLeft = minX + j * stepX;
        GLfloat xRight = minX + (j + 1) * stepX;
        GLfloat yBottom = minY + i * stepY;
        GLfloat yTop = minY + (i + 1) * stepY;

        //first triangle positions
        positions[k++] = xLeft;
        positions[k++] = yTop;
        positions[k++] = 0;

        positions[k++] = xLeft;
        positions[k++] = yBottom;
        positions[k++] = 0;

        positions[k++] = xRight;
        positions[k++] = yBottom;
        positions[k++] = 0;

        //second triangle positions
        positions[k++] = xLeft;
        positions[k++] = yTop;
        positions[k++] = 0;

        positions[k++] = xRight;
        positions[k++] = yBottom;
        positions[k++] = 0;

        positions[k++] = xRight;
        positions[k++] = yTop;
        positions[k++] = 0;
    }
}

glGenBuffers(1, &positionVbo);
glBindBuffer(GL_ARRAY_BUFFER, positionVbo);
glBufferData(GL_ARRAY_BUFFER, numTris * 3 * 3 * sizeof(float), positions, GL_STATIC_DRAW);

glVertexAttribPointer(TRI_P0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), NULL);
glVertexAttribDivisor(TRI_P0, 1);
glEnableVertexAttribArray(TRI_P0);

glVertexAttribPointer(TRI_P1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (void *)(3 * sizeof(GLfloat)));
glVertexAttribDivisor(TRI_P1, 1);
glEnableVertexAttribArray(TRI_P1);

glVertexAttribPointer(TRI_P2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (void *)(6 * sizeof(GLfloat)));
glVertexAttribDivisor(TRI_P2, 1);
glEnableVertexAttribArray(TRI_P2);

glGenBuffers(1, &colorVbo);
glBindBuffer(GL_ARRAY_BUFFER, colorVbo);
glBufferData(GL_ARRAY_BUFFER, numTris * 4 * sizeof(float), colors, GL_STATIC_DRAW);

//All color attributes are attached to the same VBO with the same stride and offset --> per-triangle colors
glVertexAttribPointer(TRI_COLOR_P0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glVertexAttribDivisor(TRI_COLOR_P0, 1);
glEnableVertexAttribArray(TRI_COLOR_P0);

glVertexAttribPointer(TRI_COLOR_P1, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glVertexAttribDivisor(TRI_COLOR_P1, 1);
glEnableVertexAttribArray(TRI_COLOR_P1);

glVertexAttribPointer(TRI_COLOR_P2, 4, GL_FLOAT, GL_FALSE, 0, NULL);
glVertexAttribDivisor(TRI_COLOR_P2, 1);
glEnableVertexAttribArray(TRI_COLOR_P2);

glBindBuffer(GL_ARRAY_BUFFER, 0);

Как видите, я использую один VBO для позиций, но каждый атрибут позиции связан с VBO с использованием другого смещения.

Для цветов я использую один VBO, и все цветовые атрибуты связаны с использованием одного и того же шага и смещения (таким образом, получая цвета для каждого треугольника вместо цветов для каждой вершины).

Цикл рендеринга выглядит следующим образом:

glUseProgram(render_program);

glUniformMatrix4fv(uniforms.mvp_matrix, 1, GL_FALSE, proj_matrix * view_matrix);

glDrawArraysInstanced(GL_TRIANGLES, 0, 3, numTris);

Я протестировал приложение на интегрированной карте Intel HD 4400 и на карте Nvidia GeForce GT 750M. Удивительно, но производительность на карте Intel намного лучше, чем на Nvidia. Вот некоторые характеристики fps:

800000 треугольников:

  • Intel: 140 кадров в секунду
  • Nvidia: 31 кадр / с

1600000 треугольников:

  • Intel: 74 кадра в секунду
  • Nvidia: 16 кадров в секунду

Кто-нибудь есть какие-либо советы о том, как улучшить производительность на карте Nvidia? Как вы думаете, использование TBO для позиций и цветов даст мне прирост производительности?

ОБНОВИТЬ:

Чтобы лучше понять проблему, я профилировал приложение под окнами, используя GPUView. Я заметил совсем другое поведение между Intel и Nvidia.

Intel генерирует один большой DMA-пакет (8 КБ) на кадр, который выполняется довольно быстро. Вместо этого Nvidia генерирует значительно большее количество маленьких пакетов (4-8 байт) в каждом кадре, которые ставятся в очередь, и по этой причине им приходится ждать много времени, прежде чем они будут выполнены.

Эта информация заставила меня задуматься, может ли это быть ошибкой драйвера Nvidia. Как вы думаете, это возможно?

0 ответов

Другие вопросы по тегам