QML 中计算着色器的段错误

Seg fault with compute shader in QML

如何在主要为 QML 的应用程序中追踪段错误?

具体错误为:*** Error in 'app/app': double free or corruption (!prev): 0x00007ff4bce5e710 ***

只有当我有一个 ComputeCommand 时才会发生(如下所示。)它本身的错误是间歇性的。这是在使用 clang 构建的 Ubuntu 14.04.03 上使用 Qt 5.7.1(同样发生在 Qt 5.8.0 和 Ubuntu 16.04)上。在调试模式下(使用 gdb)我没有得到任何有用的东西(见下文),只是我的应用程序中发生了一些事情,然后是一堆没有符号信息的 Qt3DRender 库。 Valgrind 和 Hellgrind 使应用程序变慢太多而无法使用它(这个应用程序可视化了数千个点,我无法在非常低的点数下引发问题),ASAN 和 TSAN 确实报告了问题,但没有 symbols/line我找不到任何东西。

我的设置是 main.qml -> Scene3d -> RenderSettings, Entity

我使用我编写的自定义缓冲区和几何图形(PointBufferPointGeometry)我编写但未包括在内。它们分别继承自 QBufferQGeomotry

本质上,当计算着色器被激活时(实体上的 ComputeCommand 组件,以及 viewport 上的 DispatchCompute),我得到上面的间歇性段错误。我添加的唯一异步组件是我的缓冲区被我自己的线程写入,我用 std::mutex 包围了它,但我认为这不是问题,因为我没有看到任何QBufferQGeometry 代码中的互斥。

着色器本身创建了一个连贯的缓冲区(layout (std430, binding = 0) coherent buffer Particles),但除此之外只是读入并打印出点;并在我没有任何操作的情况下工作..当然直到出现段错误。

我的问题是,我的 Compute Shader 配置不当吗?或者有更好的调试方法吗?

我能追踪到的最多的问题是它只发生在几何图形的缓冲区刷新之后。

渲染设置:

RenderSettings {
    property CameraSet cameraSet

    property real userViewWidth: 0.79
    property real topOrthoViewHeight: 0.79

    activeFrameGraph: Viewport {
        id: viewport
        normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)

        RenderSurfaceSelector {
            ClearBuffers {
                buffers:    ClearBuffers.ColorDepthBuffer

                NoDraw {}
            }

            // Compute Pass
            DispatchCompute {
                workGroupX: 1024; workGroupY: 1; workGroupZ: 1
                TechniqueFilter {
                    matchAll: [
                        FilterKey { name: "type"; value: "compute" }
                    ]
                }
            }

            Viewport {
                id: userViewport
                normalizedRect: Qt.rect(0, 0, 0.5, 1.0)

                CameraSelector {
                    id: userCameraSelectorViewport
                    camera: cameraSet.user.camera
                }
            }

            // Second and third viewport...
        }
    }
}

Material (PointMaterial)

Material {
    property PointBuffer dataBuffer;

    ShaderProgram {
        id: computeShader
        computeShaderCode:  loadSource("qrc:/shaders/pointcloud.comp")
    }

    ShaderProgram {
        id: drawShader
        vertexShaderCode:   loadSource("qrc:/shaders/pointcloud.vert")
        fragmentShaderCode: loadSource("qrc:/shaders/pointcloud.frag")
    }

    effect: Effect {
        techniques: [
            Technique {
                renderPasses: [
                    RenderPass {
                        shaderProgram: computeShader
                        parameters: [
                            // Point buffer
                            Parameter { name: "Particles"; value: dataBuffer }
                        ]
                    }
                ] // renderpasses
                filterKeys: [
                    FilterKey { name: "type"; value: "compute" }
                ]
                graphicsApiFilter {
                    api: GraphicsApiFilter.OpenGL
                    profile: GraphicsApiFilter.CoreProfile
                    majorVersion: 4
                    minorVersion: 3
                }
            },
            Technique {
                renderPasses: [
                    RenderPass {
                        shaderProgram: drawShader
                        renderStates: [
                            PointSize { sizeMode: PointSize.Programmable }
                        ]
                        parameters: [
                            Parameter { name: "pointSize"; value: 0.4 }
                        ]
                    }
                ] // renderPasses
                filterKeys: [
                    FilterKey { name: "type"; value: "draw" }
                ]
                graphicsApiFilter {
                    api: GraphicsApiFilter.OpenGL
                    profile: GraphicsApiFilter.CoreProfile
                    majorVersion: 4
                    minorVersion: 3
                }
            } // technique
        ] // techniques
    }
}

实体:

Entity {
    property PointBuffer buffer: PointBuffer {
        id: pointBuffer
        type: Buffer.VertexBuffer
    }

    PointsMaterial {
        id: pointsMaterial
        dataBuffer: pointBuffer
    }

    Entity {
        property GeometryRenderer pointRenderer: GeometryRenderer {
            instanceCount: 1 // How many times to run this geometry, since we write
                             // all points in the buffer once, only call this once.
            primitiveType: GeometryRenderer.Points
            geometry: PointGeometry { buffer: pointBuffer }
        }

        // https://doc-snapshots.qt.io/qt5-5.7/qml-computecommand.html
        property ComputeCommand computeCommand: ComputeCommand {
            workGroupX: 1024; workGroupY: 1; workGroupZ: 1
        }

        components: [ pointRenderer, computeCommand, pointsMaterial ]
    }
}

我试图限制发布的代码量以使问题变小,但我很乐意添加任何有助于解决问题的代码。

我的 (gdb) 调试器从主线程输出的是:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff9e9cc700 (LWP 8208)]
0x00007ffff33e058c in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
(gdb) bt
#0  0x00007ffff33e058c in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#1  0x00007ffff33e6619 in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#2  0x00007ffff338fe05 in Qt3DRender::Render::Renderer::performCompute(Qt3DRender::Render::RenderView const*, Qt3DRender::Render::RenderCommand*) () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#3  0x00007ffff3392610 in Qt3DRender::Render::Renderer::executeCommandsSubmission(Qt3DRender::Render::RenderView const*) () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#4  0x00007ffff3392b14 in Qt3DRender::Render::Renderer::submitRenderViews(QVector<Qt3DRender::Render::RenderView*> const&) () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#5  0x00007ffff3394bf2 in Qt3DRender::Render::Renderer::doRender() ()
   from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt53DRender.so.5
#6  0x00007fffc723a806 in ?? () from /opt/Qt5.7.1/5.7/gcc_64/qml/QtQuick/Scene3D/libqtquickscene3dplugin.so
#7  0x00007ffff1f56e3d in QMetaObject::activate(QObject*, int, int, void**) ()
   from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt5Core.so.5
#8  0x00007ffff3ef865e in QQuickWindowPrivate::renderSceneGraph(QSize const&) ()
   from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt5Quick.so.5
#9  0x00007ffff3ecabd1 in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt5Quick.so.5
#10 0x00007ffff3ecbe58 in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt5Quick.so.5
#11 0x00007ffff1d68539 in ?? () from /opt/Qt5.7.1/5.7/gcc_64/lib/libQt5Core.so.5
#12 0x00007ffff0eb3184 in start_thread (arg=0x7fff9e9cc700) at pthread_create.c:312
#13 0x00007ffff11c337d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

更新

很确定我已将其缩小为竞争条件。我已经删除了我认为可能会导致问题的其他库,但什么也没有。基本上,计算过滤器和异步重写缓冲区的组合会导致此段错误。我不确定是否有某种方法可以使用互斥锁来保护它。现在我正在寻找是否可以用 C++ 重写我的实体,以更好地控制计算着色器的执行方式。

我认为这里出现段错误的原因是我更新缓冲区的方式造成的。在我的 PointBuffer 中,我有一个调用 Qt3DRender::QBuffer::setData(new_data) 的异步任务,其中 new_dataQByteArray.

为了设置数据,我在堆栈上创建了一个 QByteArray,将我的数据复制到其中,然后使用 Qt3DRender::QBuffer::setData(my_qbyte_array)

设置我的数据

这具有将我的堆栈变量暴露给应用程序其余部分的效果,删除后会导致段错误。

我改为:

Qt3DRender::QBuffer::setData(
   QByteArray::fromRawData(
      reinterpret_cast<const char*>(points_.linearize()),
      sizeof(PointType) * pointCount
   )
)

这改为指向我的原始数据(因此不需要错误副本)并且不绑定到任何临时数据。

我提到的另一种解决方案是使用 Qt 5.8.0 的新 QBuffer::updateDatadocs函数。有了这个,我创建了一个本地 QByteArray,然后将其复制到我当前的数据中。这有问题(特别是在调整大小方面)并且效率较低,但我想我还是会提到它。