为什么 glDrawElements 在使用相同的缓冲区进行计算着色和渲染时会干扰 glGetBufferSubData

Why glDrawElements is interfering with glGetBufferSubData when using the same buffer for Compute Shading and rendering

我有一个渲染点流的程序。用于获取给定帧的点的方法具有很强的时间一致性,因此,在渲染循环中,首先,我使用计算着色器来压缩流,去除不需要的点。其次,我向集合中添加新点。最后,我使用 glDrawElements.

渲染它

这是执行该过程的代码(它使用 Qt 来操作 OpenGL):

template< typename Vec3 >
unsigned int CompactionRenderingState< Vec3 >::render()
{
    // Compact stream.
    m_nElements = compact();

    // Sends new points to GPU.
    QOpenGLBuffer* buffer = m_outputBuffers[ POS ];
    buffer->bind();
    buffer->write( m_nElements * BYTES_PER_VERTEX, ( void * ) &RenderingState::m_positions[ 0 ],
                   RenderingState::m_positions.size() * BYTES_PER_VERTEX );

    buffer = m_outputBuffers[ ATTRIB0 ];
    buffer->bind();
    buffer->write( m_nElements * BYTES_PER_VERTEX, ( void * ) &RenderingState::m_colors[ 0 ],
                   RenderingState::m_colors.size() * BYTES_PER_VERTEX );

    m_nElements += RenderingState::m_positions.size();

    // Draws the resulting points.
    m_arrayObj->bind();

    unsigned int bufferOffset = 0;
    switch( RenderingState::m_attribs )
    {
        case Attributes::NORMALS:
        {
            RenderingState::m_painter->setStandardEffect( QGL::LitMaterial );

            m_outputBuffers[ POS ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Position, 3, GL_FLOAT, GL_FALSE, 0, &bufferOffset );
            m_openGL->glEnableVertexAttribArray( QGL::Position );

            m_outputBuffers[ ATTRIB0 ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Normal, 3, GL_FLOAT, GL_FALSE, 0, &bufferOffset );
            m_openGL->glEnableVertexAttribArray( QGL::Normal );

            break;
        }
        case Attributes::COLORS:
        {
            m_renderingProgram->bind();

            m_outputBuffers[ POS ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Position, 3, GL_FLOAT, GL_FALSE, 0, &bufferOffset );
            m_openGL->glEnableVertexAttribArray( QGL::Position );

            m_outputBuffers[ ATTRIB0 ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Color, 3, GL_FLOAT, GL_FALSE, 0, &bufferOffset );
            m_openGL->glEnableVertexAttribArray( QGL::Color );

            break;
        }
        case Attributes::COLORS_AND_NORMALS:
        {
            throw logic_error( "Colors and normals not supported yet." );
            break;
        }
    }

    m_openGL->glMemoryBarrier( GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT );
    m_openGL->glDrawArrays( GL_POINTS, 0, m_nElements );

    m_openGL->glDisableVertexAttribArray( QGL::Position );
    m_openGL->glDisableVertexAttribArray( QGL::Normal );
    m_openGL->glDisableVertexAttribArray( QGL::Color );

    m_openGL->glBindBuffer( GL_ARRAY_BUFFER, 0 );
    m_renderingProgram->release();
    m_arrayObj->release();

    // Swaps buffers for the next frame.
    for( int i = 0; i < N_BUFFER_TYPES; ++i )
    {
        std::swap( m_inputBuffers[ i ], m_outputBuffers[ i ] );
    }
}

template< typename Vec3 >
unsigned int CompactionRenderingState< Vec3 >::compact()
{
    // Makes the compaction of the unused points.
    unsigned int nElements = m_compactionFlags.size();
    unsigned int nBlocks = ( unsigned int ) ceil( ( float ) nElements / BLOCK_SIZE );
    nElements = m_scan.doScan( m_compactionFlags );

    m_openGL->glBindBufferBase( GL_SHADER_STORAGE_BUFFER, Scan::N_BUFFER_TYPES + POS, m_inputBuffers[ POS ]->bufferId() );
    m_openGL->glBindBufferBase( GL_SHADER_STORAGE_BUFFER, Scan::N_BUFFER_TYPES + ATTRIB0,
                                m_inputBuffers[ ATTRIB0 ]->bufferId() );
    m_openGL->glBindBufferBase( GL_SHADER_STORAGE_BUFFER, Scan::N_BUFFER_TYPES + N_BUFFER_TYPES + POS,
                                m_outputBuffers[ POS ]->bufferId() );
    m_openGL->glBindBufferBase( GL_SHADER_STORAGE_BUFFER, Scan::N_BUFFER_TYPES + N_BUFFER_TYPES + ATTRIB0,
                                m_outputBuffers[ ATTRIB0 ]->bufferId() );

    m_compactionProgram->bind();
    m_compactionProgram->enableAttributeArray( "flags" );
    m_compactionProgram->enableAttributeArray( "prefixes" );
    m_compactionProgram->enableAttributeArray( "inputVertices" );
    m_compactionProgram->enableAttributeArray( "inputAttrib0" );
    m_compactionProgram->enableAttributeArray( "outputVertices" );
    m_compactionProgram->enableAttributeArray( "outputAttrib0" );

    m_openGL->glDispatchCompute( nBlocks, 1, 1 );
    m_openGL->glMemoryBarrier( GL_SHADER_STORAGE_BARRIER_BIT );

    m_compactionProgram->disableAttributeArray( "flags" );
    m_compactionProgram->disableAttributeArray( "prefixes" );
    m_compactionProgram->disableAttributeArray( "inputVertices" );
    m_compactionProgram->disableAttributeArray( "inputAttrib0" );
    m_compactionProgram->disableAttributeArray( "outputVertices" );
    m_compactionProgram->disableAttributeArray( "outputAttrib0" );

    m_openGL->glBindBuffer( GL_SHADER_STORAGE_BUFFER, 0 );

    return nElements;
}

====== 已编辑 ======

我写了一个自动化测试来检查整个过程。该测试以删除奇数索引中的数据的方式压缩一个点位置数组和另一个属性。当我 运行 gdb 下的代码禁用了地址 space 随机化时,它完美地通过了。但是,当 运行 没有 gdb 或在 gdb 中启用地址 space 随机化时,返回的数组全为零,除非我在方法 render().[=19= 中注释 glDrawArrays ]

======编辑结束======

TEST_F( CompactionTest, Compaction )
    {
        QGuiApplication app( g_argc, g_argv );

        QSurfaceFormat format;
        format.setVersion( 4, 3 );
        format.setRenderableType( QSurfaceFormat::OpenGL );
        format.setSwapBehavior( QSurfaceFormat::DoubleBuffer );
        format.setSamples( 16 );

        unsigned int nElements = 3000;
        vector< unsigned int  > flags( nElements );
        vector< vec3 > pos( nElements );
        vector< vec3 > attrib0( nElements );

        for( int i = 0; i < nElements; ++i )
        {
            flags[ i ] = i % 2;
            pos[ i ] = vec3( i, i, i );
            attrib0[ i ] = vec3( i + nElements, i + nElements, i + nElements );
        }

        CompactionQGLView window( flags, pos, attrib0, format );
        window.resize(640, 480);
        window.show();

        app.exec();

        pos = window.m_compactedPos;
        attrib0 = window.m_compactedAttrib0;

        ASSERT_EQ( pos.size(), nElements * 0.5 );
        ASSERT_EQ( attrib0.size(), nElements * 0.5 );

        float expected = 1.;
        for( int i = 0; i < pos.size(); ++i, expected += 2 )
        {
            vec3 expectedVec( expected, expected, expected );
            cout << "Pos: " << pos[ i ] << ". Expected: " << expectedVec << endl;
            ASSERT_EQ( pos[ i ], expectedVec );

            expectedVec = vec3( expected + nElements, expected + nElements, expected + nElements );
            cout << "Attrib0: " << attrib0[ i ] << ". Expected: " << expectedVec << endl << endl;
            ASSERT_EQ( attrib0[ i ], expectedVec );
        }
    }

下一个函数读取压缩结果,并在测试中被window用来设置m_compactedPosm_compactedAttrib0

template< typename Vec3 >
vector< vector< Vec3 > > CompactionRenderingState< Vec3 >::getResultCPU()
{
    m_openGL->glMemoryBarrier( GL_SHADER_STORAGE_BARRIER_BIT );

    unsigned int resultSize = sizeof( Vec3 ) * m_nElements;
    Vec3* result = ( Vec3* ) malloc( resultSize );

    vector< vector< Vec3 > > results;

    for( int i = 0; i < N_BUFFER_TYPES; ++i )
    {
        if( m_inputBuffers[ i ] != NULL )
        {
            m_openGL->glBindBuffer( GL_SHADER_STORAGE_BUFFER, m_inputBuffers[ i ]->bufferId() );
            m_openGL->glGetBufferSubData( GL_SHADER_STORAGE_BUFFER, 0, resultSize, ( void * ) result );

            vector< Vec3 > tempVec( m_nElements );
            std::copy( result, result + m_nElements, tempVec.begin() );
            results.push_back( tempVec );
        }
    }

    free( result );

    return results;
}

====== 已编辑 ======

那么,只有在启用地址 Space 随机化时才会出现此错误的可能原因是什么?我被困住了,已经花了几天时间试图解决这个问题。有什么想法吗?

======编辑结束======

最终问题是在调用 glVertexAttribPointer 时出现了一个微妙的错误隐式类型转换。

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) 接收最后一个参数 a const GLvoid * pointer。在我的代码中,我将变量 unsigned int bufferOffset 的地址作为最后一个参数传递。这应该会导致 OpenGL 出现一些内部混乱。

要修复代码,我们只需更改 render() 函数中 switch 子句的 glVertexAttribPointer 调用:

switch( RenderingState::m_attribs )
    {
        case Attributes::NORMALS:
        {
            RenderingState::m_painter->setStandardEffect( QGL::LitMaterial );

            m_outputBuffers[ POS ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Position, 3, GL_FLOAT, GL_FALSE, 0, ( void * ) 0 );
            m_openGL->glEnableVertexAttribArray( QGL::Position );

            m_outputBuffers[ ATTRIB0 ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Normal, 3, GL_FLOAT, GL_FALSE, 0, ( void * ) 0 );
            m_openGL->glEnableVertexAttribArray( QGL::Normal );

            break;
        }
        case Attributes::COLORS:
        {
            m_renderingProgram->bind();

            m_outputBuffers[ POS ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Position, 3, GL_FLOAT, GL_FALSE, 0, ( void * ) 0 );
            m_openGL->glEnableVertexAttribArray( QGL::Position );

            m_outputBuffers[ ATTRIB0 ]->bind();
            m_openGL->glVertexAttribPointer( QGL::Color, 3, GL_FLOAT, GL_FALSE, 0, ( void * ) 0 );
            m_openGL->glEnableVertexAttribArray( QGL::Color );

            break;
        }
        case Attributes::COLORS_AND_NORMALS:
        {
            throw logic_error( "Colors and normals not supported yet." );
            break;
        }
    }