我在将 GPU 缓冲区传输到 CPU 以执行排序操作时遇到了一些问题。缓冲区是由 300.000 个浮点值组成的GL_SHADER_STORAGE_BUFFER
。使用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 是一个计算着色器,但我可能不存在问题,因为如果我注释运行命令,读取需要完全相同的时间。
奇怪的是,如果我执行只有 1 个浮点数的 getbuffer:
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER,0, 1 *sizeof(float),&row[0]);
它需要完全相同的时间...所以我猜一路上都有问题...也许与GL_SHADER_STORAGE_BUFFER
有关?
这很可能是 GPU-CPU 同步/往返导致延迟。 即,一旦映射缓冲区,接触缓冲区的先前 GL 命令需要立即完成,从而导致管道停止。 请注意,驱动程序是懒惰的:很可能 GL 命令甚至还没有开始执行。
如果可以:持久glBufferStorage(..., GL_MAP_PERSISTENT_BIT)
和映射缓冲区。这避免了完全重新映射和分配任何 GPU 内存,您可以将映射的指针保留在绘制调用上,但需要注意一些:
- 您可能还需要 GPU 围栏来检测/等待数据何时实际可从 GPU 获得。(除非你喜欢看垃圾。
- 无法调整映射缓冲区的大小。(因为你已经使用了glBufferStorage((,所以没关系(
- 将GL_MAP_PERSISTENT_BIT与GL_MAP_COHERENT_BIT结合起来可能是个好主意
在阅读了更多 GL 4.5 文档后,我发现glFenceSync
是强制性的,以确保数据已从 GPU 到达,即使有GL_MAP_COHERENT_BIT:
如果设置了GL_MAP_COHERENT_BIT并且服务器执行写入,则应用必须调用 glFenceSync 与GL_SYNC_GPU_COMMANDS_COMPLETE(或 glFinish(。然后 CPU 将在同步后看到写入 完成。