【问题标题】:glfwSwapBuffers slow (>3s)glfwSwapBuffers 慢(>3 秒)
【发布时间】:2022-12-04 21:20:51
【问题描述】:

我编写了一个程序,在计算着色器上进行一些计算,然后显示返回的数据。这完美地工作,除了在着色器运行时程序执行被阻止(见下面的代码)并且根据参数,这可能需要一段时间:

void CalculateSomething(GLfloat* Result)
{
    // load some uniform variables
    glDispatchCompute(X, Y, 1);
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
    GLfloat* mapped = (GLfloat*)(glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY));
    memcpy(Result, mapped, sizeof(GLfloat) * X * Y);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

void main
{
    // Initialization stuff
    // ...

    while (glfwWindowShouldClose(Window) == 0)
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glfwPollEvents();
        glfwSwapInterval(2); // Doesn't matter what I put here

        CalculatateSomething(Result);
        Render(Result);

        glfwSwapBuffers(Window.WindowHandle);
    }
}

为了在计算着色器计算时保持主循环运行,我将 CalculateSomething 更改为如下所示:

void CalculateSomething(GLfloat* Result)
{
    // load some uniform variables
    glDispatchCompute(X, Y, 1);
    GPU_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}

bool GPU_busy()
{
    GLint GPU_status;
    if (GPU_sync == NULL)
        return false;
    else
    {
        glGetSynciv(GPU_sync, GL_SYNC_STATUS, 1, nullptr, &GPU_status);
        return GPU_status == GL_UNSIGNALED;
    }
}

这两个函数是一个类的一部分,如果我必须在这里发布所有内容,它会变得有点混乱和复杂(如果需要更多代码,请告诉我)。因此,当类被告知进行计算时,每个循环都会首先检查 GPU 是否忙碌。如果完成,结果将被复制到 CPU 内存(或开始计算),否则它返回到 main 而不做任何其他事情。无论如何,这种方法之所以有效,是因为它会产生正确的结果。但是我的主循环仍然被阻塞。

做一些计时显示CalculateSomethingRender(以及其他一切)运行得很快(正如我所期望的那样)。但现在 glfwSwapBuffers 需要 >3000 毫秒(取决于计算着色器的计算时间)。

在计算着色器运行时是否应该可以切换缓冲区?渲染结果似乎工作正常且没有延迟(只要计算着色器尚未完成,就应该渲染旧结果)。或者我在这里遗漏了什么(排队的 OpenGL 调用在 glfwSwapBuffers 做某事之前得到处理?)?

【问题讨论】:

  • 看起来您正在使用 glfwSwapInterval() 来控制应用程序的帧速率。但是,此函数仅影响缓冲区交换的速率,而不影响应用程序运行的速率。换句话说,无论您传递给 glfwSwapInterval() 的值如何,您的应用程序都将继续尽可能快地运行。
  • 要解决此问题,您可以使用不同的机制来控制应用程序的帧速率。一种方法是使用计时器测量自上一帧以来经过的时间,然后使用此信息来决定是否渲染新帧。

标签: c++ opengl glfw


【解决方案1】:

您可以使用双缓冲技术来代替使用栅栏同步来检查 GPU 是否繁忙,您可以使用双缓冲技术,其中有两组输出缓冲区,一组当前正在渲染,另一组正在由计算着色器写入。这样,当计算着色器在后台运行时,主循环可以连续交换前后缓冲区。

下面是一些伪代码来说明这种方法:

// Initialize the front and back buffer
GLfloat* front_buffer = (GLfloat*)(glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY));
GLfloat* back_buffer = (GLfloat*)(glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY));

// Update function that is called in the main loop
void Update()
{
  // If the compute shader is currently running, do nothing
  if (compute_shader_running)
  return;


  // Swap the front and back buffer
  std::swap(front_buffer, back_buffer);

  // Dispatch the compute shader with the new back buffer
  glDispatchCompute(X, Y, 1);
  glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

  // Set the compute_shader_running flag
  compute_shader_running = true;
}

// Render function that is called in the main loop
void Render(GLfloat* Result)
{
  // Use the current front buffer to render
  memcpy(Result, front_buffer, sizeof(GLfloat) * X * Y);


  // If the compute shader is done running, set the flag to false
  if (compute_shader_done)
    compute_shader_running = false;
  }

void main
{
  // Initialization stuff
  // ...


  while (glfwWindowShouldClose(Window) == 0)
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glfwPollEvents();
    glfwSwapInterval(2);

    Update();
    Render(Result);

    glfwSwapBuffers(Window.WindowHandle);
  }
}

请注意,这只是一种可能的解决方案,您可能需要根据您的具体要求调整代码。此外,上面的代码仅用于说明目的,可能无法按原样编译。

【讨论】:

  • 我不认为这样做,我想做的是:调用 glMemoryBarrier 它将阻塞主线程/循环,直到计算着色器完成(这等同于我的第一种方法)。关键是,主循环应该继续运行(和渲染),因为那里有一些 ImGUI 的东西......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-04
  • 1970-01-01
  • 2021-11-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
相关资源
最近更新 更多