opengl glfwSwapBuffers slow(>3s)/如何在计算着色器执行较长计算时使主循环平稳运行?

hwazgwia  于 2023-02-22  发布在  其他
关注(0)|答案(1)|浏览(198)
    • bounty将在6天后过期**。回答此问题可获得+50的声望奖励。Paul Aner正在寻找规范答案:我想提出这个问题的原因很清楚:我希望主循环在计算着色器处理大量数据时不锁定。我可以尝试将数据分成更小的片段,但如果计算是在CPU上完成的,我只需启动一个线程,一切都会运行得很好很顺利。尽管如此,我当然必须等到计算线程提供新的数据来更新屏幕-GUI(ImGUI)不会锁定...

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

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需要〉3000ms(取决于计算着色器的计算时间)。
在计算着色器运行时,是否可以切换缓冲区?渲染结果似乎工作正常,没有延迟(只要计算着色器还没有完成,旧的结果就应该被渲染)。或者我错过了什么(排队的OpenGL调用在glfwSwapBuffers执行某些操作之前得到处理?)

    • 编辑:**

我不知道为什么这个问题被关闭了,还需要什么额外的信息(也许除了操作系统,应该是Windows)。至于"期望的行为":我希望glfwSwapBuffers-调用不会阻塞我的主循环。要了解更多信息,请询问...
正如Erdal Küçük所指出的,glFlush的隐式调用可能会导致延迟。出于测试目的,我确实将此调用放在glfwSwapBuffer之前,并对其进行了计时-这里没有延迟...
我确定,我不可能是唯一一个遇到这个问题的人。也许有人可以尝试重现它?简单地把一个计算着色器放在主循环中,它需要几秒钟来完成它的计算。我在哪里读到过类似的问题发生,特别是在调用glMapBuffer时。这似乎是GPU驱动程序的问题(我的将是一个集成的英特尔GPU)。但没有任何地方我读到有关延迟超过200毫秒...

gk7wooem

gk7wooem1#

解决了GL_PIXEL_PACK_BUFFER有效用作屏幕外计算着色器的类似问题。使用围栏的方法是正确的,但您需要有一个单独的函数,使用glGetSynciv来读取GL_SYNC_STATUS,以检查围栏的状态。解决方案(在Java中公认)can be found here
为什么这是必要的解释可以在:在@Nick Clark评论中回答:
OpenGL中的每个调用都是异步的,除了帧缓冲区交换,它会暂停调用线程,直到所有提交的函数都执行完毕。因此,glfwSwapBuffers似乎需要这么长时间。
溶液的相关部分为:

public void finishHMRead( int pboIndex ){
    int[] length = new int[1];
    int[] status = new int[1];
    GLES30.glGetSynciv( hmReadFences[ pboIndex ], GLES30.GL_SYNC_STATUS, 1, length, 0, status, 0 );
    int signalStatus = status[0];
    int glSignaled   = GLES30.GL_SIGNALED;
    if( signalStatus == glSignaled ){
        // Ready a temporary ByteBuffer for mapping (we'll unmap the pixel buffer and lose this) and a permanent ByteBuffer
        ByteBuffer pixelBuffer;
        texLayerByteBuffers[ pboIndex ] = ByteBuffer.allocate( texWH * texWH );

        // map data to a bytebuffer
        GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, pbos[ pboIndex ] );
        pixelBuffer = ( ByteBuffer ) GLES30.glMapBufferRange( GLES30.GL_PIXEL_PACK_BUFFER, 0, texWH * texWH * 1, GLES30.GL_MAP_READ_BIT );
        
        // Copy to the long term ByteBuffer
        pixelBuffer.rewind(); //copy from the beginning
        texLayerByteBuffers[ pboIndex ].put( pixelBuffer );
        
        // Unmap and unbind the currently bound pixel buffer
        GLES30.glUnmapBuffer( GLES30.GL_PIXEL_PACK_BUFFER );
        GLES30.glBindBuffer( GLES30.GL_PIXEL_PACK_BUFFER, 0 );
        Log.i( "myTag", "Finished copy for pbo data for " + pboIndex + " at: " + (System.currentTimeMillis() - initSphereStart) );
        acknowledgeHMReadComplete();
    } else {
        // If it wasn't done, resubmit for another check in the next render update cycle
        RefMethodwArgs finishHmRead = new RefMethodwArgs( this, "finishHMRead", new Object[]{ pboIndex } );
        UpdateList.getRef().addRenderUpdate( finishHmRead );
    }
}

基本上,关闭计算机着色器,然后等待GL_SYNC_STATUSglGetSynciv检查等于GL_SIGNALED,然后重新绑定GL_SHADER_STORAGE_BUFFER并执行glMapBuffer操作。

相关问题