【问题标题】:Write from compute shader to persistently mapped SSBO fails从计算着色器写入持久映射的 SSBO 失败
【发布时间】:2018-07-05 00:36:43
【问题描述】:

我正在尝试使用计算着色器写入 SSBO,并在 cpu 上读回数据。

计算着色器只是一个 1x1x1 的玩具示例,可写入 24 个浮点数:

#version 450 core

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout (std430, binding = 0) buffer particles {
    float Particle[];
};

void main() {
    for (int i = 0; i < 24; ++i) {
        Particle[i] = i + 1;
    }
}

这就是我运行着色器和读取数据的方式:

val bufferFlags = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT
val bufferSize = 24 * 4
val bufferId = glCreateBuffers()

glNamedBufferStorage(bufferId, bufferSize, bufferFlags)

val mappedBuffer = glMapNamedBufferRange(bufferId, 0, bufferSize, bufferFlags)

mappedBuffer.rewind()
val mappedFloatBuffer = mappedBuffer.asFloatBuffer()

mappedFloatBuffer.rewind()

val ssboIndex = glGetProgramResourceIndex(progId, GL_SHADER_STORAGE_BLOCK, "particles")

val props = Array(GL_BUFFER_BINDING)
val params = Array(-1)
glGetProgramResourceiv(progId, GL_SHADER_STORAGE_BLOCK, ssboIndex, props, null, params)

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, params(0), bufferId)

glUseProgram(progId)

val sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
glDispatchCompute(1, 1, 1)

glClientWaitSync(sync, 0, 1000000000) match {
  case GL_TIMEOUT_EXPIRED =>
    println("Timeout expired")

  case GL_WAIT_FAILED =>
    println("Wait failed. " + glGetError())

  case _ =>
    println("Result:")

    while(mappedFloatBuffer.hasRemaining) {
      println(mappedFloatBuffer.get())
    }
}

我希望它打印数字 1 到 24,但它会打印 24 个零。使用 cpu 我可以读取和写入(如果设置了GL_MAP_WRITE_BIT)到缓冲区就好了。如果我不使用 DSA(glBindBuffer / glBufferStorage / glMapBufferRange 代替),也会发生同样的情况。但是,如果在着色器运行时未映射缓冲区并且我仅在打印内容之前对其进行映射,则一切正常。这不正是持久映射缓冲区的用途吗?所以我可以在 gpu 使用它时保持它的映射?

我检查了任何错误,glGetError 以及较新的调试输出,但我没有得到任何错误。

Here (pastebin) 是一个完整的示例。你需要LWJGL 来运行它。

【问题讨论】:

    标签: scala opengl lwjgl opengl-4


    【解决方案1】:

    您的代码中存在许多问题。

    首先,将栅栏同步放在要与之同步的命令之前。与栅栏同步会与栅栏之前执行的所有命令同步,而不是在栅栏之后。如果要与计算着色器执行同步,则必须在调度调用之后插入栅栏,而不是之前。

    第二,同步是不够的。对 SSBO 的写入是不连贯的,因此您必须遵循 incoherent memory accesses 的规则才能使它们对您可见。在这种情况下,您需要在计算操作和尝试使用glMemoryBarrier 从缓冲区读取数据之间插入适当的内存屏障。由于您是通过映射读取数据,因此正确的使用障碍是GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT

    当您使用非持久映射时,您的代码似乎可以正常工作,但这只是表象。由于不正确的不连贯内存访问(即:缺少内存屏障),它仍然是未定义的行为。恰好 UB 做你想做的事……在这种情况下。

    【讨论】:

    • 事后看来,这很明显。无论如何,感谢您指出。但是,我不认为内存屏障是必要的,因为缓冲区是用GL_MAP_COHERENT_BIT 映射的。
    • @IchBinKeinBaum:是的,这是必要的。连贯的一点是关于您是否需要刷新/使缓冲区无效以使 GPU 看到您编写的内容/以便您查看 GPU 编写的内容。您仍然需要确保计算着色器使用的缓存已被清除,以便来自 CS 的写入实际上在 GPU 内存中。这就是障碍的用途。
    • The docs 具体说“如果设置了 GL_MAP_COHERENT_BIT 并且服务器进行了写入,则应用程序必须使用 GL_SYNC_GPU_COMMANDS_COMPLETE(或 glFinish)调用 FenceSync。然后 CPU 将在同步完成后看到写入。 "应该不需要内存屏障。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 2015-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-27
    相关资源
    最近更新 更多