glGetBufferSubData и glMapBufferRange для GL_SHADER_STORAGE_BUFFER очень медленные на NVIDIA GTX960M

У меня возникли проблемы с передачей буфера графического процессора в процессор для выполнения операций сортировки. Буфер представляет собой GL_SHADER_STORAGE_BUFFER, состоящий из 300 000 значений с плавающей запятой. Операция передачи с glGetBufferSubData занимает около 10 мс, а с glMapBufferRange — более 100 мс.

Код, который я использую, следующий:

std::vector<GLfloat> viewRow;
unsigned int viewRowBuffer = -1;
int length = -1;

void bindRowBuffer(unsigned int buffer){
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, buffer);
}

void initRowBuffer(unsigned int &buffer, std::vector<GLfloat> &row, int lengthIn){
    // Generate and initialize buffer
    length = lengthIn;
    row.resize(length);
    memset(&row[0], 0, length*sizeof(float));
    glGenBuffers(1, &buffer);
    bindRowBuffer(buffer);
    glBufferStorage(GL_SHADER_STORAGE_BUFFER, row.size() * sizeof(float), &row[0], GL_DYNAMIC_STORAGE_BIT | GL_MAP_READ_BIT | GL_MAP_WRITE_BIT);

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}

void cleanRowBuffer(unsigned int buffer) {
    float zero = 0.0;
    glClearNamedBufferData(buffer, GL_R32F, GL_RED, GL_FLOAT, &zero);
}

void readGPUbuffer(unsigned int buffer, std::vector<GLfloat> &row) {
    glGetBufferSubData(GL_SHADER_STORAGE_BUFFER,0,length *sizeof(float),&row[0]);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
}

void readGPUMapBuffer(unsigned int buffer, std::vector<GLfloat> &row) {
    float* data = (float*)glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, length*sizeof(float), GL_MAP_READ_BIT); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
     memcpy(&row[0], data, length *sizeof(float));
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

Основное делается:

    bindRowBuffer(viewRowBuffer);
    cleanRowBuffer(viewRowBuffer);
    countPixs.bind();
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, gPatch);
    countPixs.setInt("gPatch", 0);
    countPixs.run(SCR_WIDTH/8, SCR_HEIGHT/8, 1);
    countPixs.unbind();
    readGPUbuffer(viewRowBuffer, viewRow);

Где countPixs - это вычислительный шейдер, но я уверен, что проблема не в нем, потому что, если я прокомментирую команду запуска, чтение займет ровно столько же времени.

Странно то, что если я выполню getbuffer только с 1 числом с плавающей запятой:

glGetBufferSubData(GL_SHADER_STORAGE_BUFFER,0, 1 *sizeof(float),&row[0]);

Это занимает ровно столько же времени... так что я предполагаю, что все время что-то не так... может быть, это связано с GL_SHADER_STORAGE_BUFFER?


person jpaguerre    schedule 30.05.2020    source источник
comment
Вам нужен доступ на запись к данным или только чтение?   -  person Makogan    schedule 05.06.2020
comment
только чтение нормально   -  person jpaguerre    schedule 06.06.2020
comment
Более простой способ добиться того, что вы хотите на более высоких скоростях, может заключаться в использовании массива одномерных текстур вместо SSBO.   -  person Makogan    schedule 06.06.2020


Ответы (1)


Это, вероятно, вызвано синхронизацией GPU-CPU/задержкой туда и обратно. т.е. как только вы сопоставите свой буфер, предыдущие команды GL, которые коснулись буфера, должны быть немедленно завершены, что приведет к остановке конвейера. Обратите внимание, что драйверы ленивы: весьма вероятно, что команды GL еще даже не начали выполняться.

Если вы можете: glBufferStorage(..., GL_MAP_PERSISTENT_BIT) и постоянно сопоставлять буфер. Это позволяет избежать полного переназначения и выделения любой памяти графического процессора, и вы можете сохранить сопоставленный указатель над вызовами отрисовки с некоторыми оговорками:

  • Вам, вероятно, также нужны ограничения GPU, чтобы обнаруживать/ждать, когда данные действительно доступны с GPU. (Если вам не нравится читать мусор.)
  • Сопоставленный буфер не может быть изменен. (поскольку вы уже используете glBufferStorage(), все в порядке)
  • Вероятно, хорошей идеей будет объединить GL_MAP_PERSISTENT_BIT с GL_MAP_COHERENT_BIT.

Прочитав GL 4.5 docs, я узнал больше что glFenceSync является обязательным, чтобы гарантировать получение данных от графического процессора, даже с GL_MAP_COHERENT_BIT:

Если GL_MAP_COHERENT_BIT установлен и сервер выполняет запись, приложение должно вызвать glFenceSync с GL_SYNC_GPU_COMMANDS_COMPLETE (или glFinish). Затем ЦП увидит записи после завершения синхронизации.

person JATothrim    schedule 05.06.2020