3

I would like to implement a GCode Viewer in 3D for a C++/Qt5.15.2 program that I wrote.

A GCode file contains the instructions for a 3D-Printer to print a 3D Model (where to move, how much material to extrude, which layer is being printed, etc.). After parsing the GCode file, I get a list of lines representing the displacement of the nozzle and want to display in 3D this basic list of lines. Like in this example from https://gcode.ws/ 3D Render of a GCode File

I started playing with Qt3D and OpenGL examples from Qt to try to draw a lot of lines (in this case 10.000, but some GCode have more than a million lines) but after creating the scene and trying to display the window the program crashes. How do you draw a lot of objects in Qt3D or openGL without the program crashing please ?

I started from this example, removed the example elements from the scene, and used a drawLine function from this thread to draw my lines. What am I doing wrong please ? My code is :

#include <QGuiApplication>

#include <Qt3DCore/QEntity>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QCameraLens>
#include <Qt3DCore/QTransform>
#include <Qt3DCore/QAspectEngine>

#include <Qt3DInput/QInputAspect>

#include <Qt3DRender/QRenderAspect>
#include <Qt3DExtras/QForwardRenderer>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QTorusMesh>

#include <QPropertyAnimation>

#include "qt3dwindow.h"


#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DRender/QGeometry>

#include <QElapsedTimer>


void drawLine(const QVector3D& start, const QVector3D& end, const QColor& color, Qt3DCore::QEntity *_rootEntity)
{
    auto *geometry = new Qt3DRender::QGeometry(_rootEntity);

    // position vertices (start and end)
    QByteArray bufferBytes;
    bufferBytes.resize(3 * 2 * sizeof(float)); // start.x, start.y, start.end + end.x, end.y, end.z
    float *positions = reinterpret_cast<float*>(bufferBytes.data());
    *positions++ = start.x();
    *positions++ = start.y();
    *positions++ = start.z();
    *positions++ = end.x();
    *positions++ = end.y();
    *positions++ = end.z();

    auto *buf = new Qt3DRender::QBuffer(geometry);
    buf->setData(bufferBytes);

    auto *positionAttribute = new Qt3DRender::QAttribute(geometry);
    positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
    positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
    positionAttribute->setVertexSize(3);
    positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    positionAttribute->setBuffer(buf);
    positionAttribute->setByteStride(3 * sizeof(float));
    positionAttribute->setCount(2);
    geometry->addAttribute(positionAttribute); // We add the vertices in the geometry

    // connectivity between vertices
    QByteArray indexBytes;
    indexBytes.resize(2 * sizeof(unsigned int)); // start to end
    unsigned int *indices = reinterpret_cast<unsigned int*>(indexBytes.data());
    *indices++ = 0;
    *indices++ = 1;

    auto *indexBuffer = new Qt3DRender::QBuffer(geometry);
    indexBuffer->setData(indexBytes);

    auto *indexAttribute = new Qt3DRender::QAttribute(geometry);
    indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);
    indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
    indexAttribute->setBuffer(indexBuffer);
    indexAttribute->setCount(2);
    geometry->addAttribute(indexAttribute); // We add the indices linking the points in the geometry

    // mesh
    auto *line = new Qt3DRender::QGeometryRenderer(_rootEntity);
    line->setGeometry(geometry);
    line->setPrimitiveType(Qt3DRender::QGeometryRenderer::Lines);
    auto *material = new Qt3DExtras::QPhongMaterial(_rootEntity);
    material->setAmbient(color);

    // entity
    auto *lineEntity = new Qt3DCore::QEntity(_rootEntity);
    lineEntity->addComponent(line);
    lineEntity->addComponent(material);
}


int main(int argc, char* argv[])
{
    QGuiApplication app(argc, argv);
    Qt3DExtras::Qt3DWindow view;

    Qt3DCore::QEntity *scene = new Qt3DCore::QEntity;

    // Camera
    Qt3DRender::QCamera *camera = view.camera();
    camera->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
    camera->setPosition(QVector3D(0, 0, 2.0f));
    camera->setViewCenter(QVector3D(0, 0, 0));

    QColor red(255,0,0);

    QElapsedTimer elapsed;
    elapsed.start();
    double i=0;
    while( i < 1000){
        qDebug() << i << elapsed.elapsed();
        drawLine(QVector3D(0,0,-i/10.0),QVector3D(1,0,-i/10.0),red,scene);
        drawLine(QVector3D(1,0,-i/10.0),QVector3D(1,1,-i/10.0),red,scene);
        drawLine(QVector3D(1,1,-i/10.0),QVector3D(0,1,-i/10.0),red,scene);
        drawLine(QVector3D(0,1,-i/10.0),QVector3D(0,0,-i/10.0),red,scene);
        i+=0.1;
    }

    // For camera controls (commented to publish less code)
    /*Qt3DExtras::QOrbitCameraController *camController = new Qt3DExtras::QOrbitCameraController(scene);
    camController->setLinearSpeed( 50.0f );
    camController->setLookSpeed( 180.0f );
    camController->setCamera(camera);*/

    view.setRootEntity(scene);
    view.show();

    return app.exec();
}

If usefull, here is the debug stack :

1   __memmove_avx_unaligned_erms                                                                                      memmove-vec-unaligned-erms.S 369 0x7ffff6276828 
2   ??                                                                                                                                                 0x7fffdb31a2cb 
3   ??                                                                                                                                                 0x7fffdb307d68 
4   ??                                                                                                                                                 0x7fffdb317c46 
5   ??                                                                                                                                                 0x7fffda6ac0ad 
6   ??                                                                                                                                                 0x7fffda6acd23 
7   ??                                                                                                                                                 0x7fffda6ae2ee 
8   ??                                                                                                                                                 0x7fffda6ae91d 
9   ??                                                                                                                                                 0x7fffda6ce86e 
10  ??                                                                                                                                                 0x7fffda6f4167 
11  ??                                                                                                                                                 0x7fffda953428 
12  ??                                                                                                                                                 0x7fffda820a05 
13  ??                                                                                                                                                 0x7fffda820df8 
14  ??                                                                                                                                                 0x7fffda820f55 
15  ??                                                                                                                                                 0x7fffda9486f8 
16  Qt3DRender::Render::OpenGL::Renderer::submitRenderViews(QVector<Qt3DRender::Render::OpenGL::RenderView *> const&)                                  0x7fffe8045819 
17  Qt3DRender::Render::OpenGL::Renderer::doRender(bool)                                                                                               0x7fffe8046b83 
18  Qt3DRender::Render::OpenGL::Renderer::render()                                                                                                     0x7fffe803ae79 
19  Qt3DRender::Render::RenderThread::run()                                                                                                            0x7ffff7939c3e 
20  QThreadPrivate::start(void *)                                                                                                                      0x7ffff65adb35 
... <More>           
BDL
  • 21,052
  • 22
  • 49
  • 55
ElevenJune
  • 423
  • 1
  • 4
  • 21
  • You need to set the `vertexSize` and the `byteStride` on the indexBuffer too. Maybe that's the problem. But your solution drawing single lines seems very inefficient. For improving efficiency try to provide all lines and indices to draw in one vertex and index buffer. – vre Aug 25 '22 at 10:09
  • Thanks for your comment ! I tried to set `vertexSide` and `byteStride` on the indexBuffer too. It's not crashing anymore but the CPU goes crazy and the window never renders. I had to kill the process. For the second part, what is the function do draw multiple objects at once ? I found one in openGL (`glMultiDrawElements`) but it doesn't seem to be available in Qt3D. – ElevenJune Aug 25 '22 at 14:36

1 Answers1

2

I would propose using a drawLineStrip function to speed up the drawing. Vertices and indices of the same material are placed in a single vertex/index buffer. It is important to set the setRestartIndexValue and setPrimitiveRestartEnabled properties of the GeometryRenderer. It would look as follows:

void drawLineStrip(const QVector<QVector3D>& vertices, const QVector<unsigned int>& indices, const QColor& color, Qt3DCore::QEntity* _rootEntity)
{
    auto geometry = new Qt3DRender::QGeometry(_rootEntity);

    // position vertices (start and end)
    QByteArray bufferBytes;
    bufferBytes.resize(3 * vertices.size() * sizeof(float)); // start.x, start.y, start.end + end.x, end.y, end.z
    auto positions = reinterpret_cast<float*>(bufferBytes.data());
    for (int n = vertices.size(), i = 0; i < n; ++i)
    {
        *positions++ = vertices[i].x();
        *positions++ = vertices[i].y();
        *positions++ = vertices[i].z();
    }

    auto buf = new Qt3DRender::QBuffer(geometry);
    buf->setData(bufferBytes);

    auto positionAttribute = new Qt3DRender::QAttribute(geometry);
    positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
    positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
    positionAttribute->setVertexSize(3);
    positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    positionAttribute->setBuffer(buf);
    positionAttribute->setByteStride(3 * sizeof(float));
    positionAttribute->setCount(vertices.size());
    geometry->addAttribute(positionAttribute); // We add the vertices in the geometry

    // connectivity between vertices
    QByteArray indexBytes;
    indexBytes.resize(indices.size() * sizeof(unsigned int)); // start to end
    memcpy(indexBytes.data(), indices.data(), indices.size() * sizeof(unsigned int));

    auto indexBuffer = new Qt3DRender::QBuffer(geometry);
    indexBuffer->setData(indexBytes);

    auto indexAttribute = new Qt3DRender::QAttribute(geometry);
    indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt);
    indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
    indexAttribute->setVertexSize(1);
    indexAttribute->setBuffer(indexBuffer);
    indexAttribute->setByteStride(1 * sizeof(unsigned int));
    indexAttribute->setCount(indices.size());
    geometry->addAttribute(indexAttribute); // We add the indices linking the points in the geometry

    // mesh
    auto line = new Qt3DRender::QGeometryRenderer(_rootEntity);
    line->setGeometry(geometry);
    line->setRestartIndexValue(-1);
    line->setPrimitiveRestartEnabled(true);
    line->setPrimitiveType(Qt3DRender::QGeometryRenderer::LineStrip);
    auto material = new Qt3DExtras::QPhongMaterial(_rootEntity);
    material->setAmbient(color);

    // entity
    auto lineEntity = new Qt3DCore::QEntity(_rootEntity);
    lineEntity->addComponent(line);
    lineEntity->addComponent(material);
}

In your program you would use it this way:

QVector<QVector3D> vertices;
vertices.resize((ceil(1000. / 0.1) + 1) * 5);
QVector<unsigned int> indices;
indices.resize((ceil(1000. / 0.1) + 1) * 6);
double i = 0;
int k = 0;
while (i < 1000) {
    int vIdx = 5 * k;
    vertices[vIdx + 0] = QVector3D(0, 0, -i / 10.0);
    vertices[vIdx + 1] = QVector3D(1, 0, -i / 10.0);
    vertices[vIdx + 2] = QVector3D(1, 1, -i / 10.0);
    vertices[vIdx + 3] = QVector3D(0, 1, -i / 10.0);
    vertices[vIdx + 4] = QVector3D(0, 0, -i / 10.0);
    int idx = 6 * k;
    indices[idx + 0] = vIdx + 0;
    indices[idx + 1] = vIdx + 1;
    indices[idx + 2] = vIdx + 2;
    indices[idx + 3] = vIdx + 3;
    indices[idx + 4] = vIdx + 4;
    indices[idx + 5] = -1;
    i += 0.1;
    ++k;
}
drawLineStrip(vertices, indices, red, scene);
vre
  • 6,041
  • 1
  • 25
  • 39
  • Thanks a lot for your answer ! I just tested it and it's amazing how performant it is ! I could render a million lines in no time ! How is it possible that's it so much more performant ? What is offering this result ? Thanks a lot, again ! You helped me a lot ! – ElevenJune Aug 25 '22 at 15:17
  • The scenegraph is very small (1 graphics element) compared to your original one (50000 graphics elements). If you look on the memory footprint you'll see a 100 time improvement over your original solution. As a rule of thumb, less but larger elements will improve the rendering speed. As all lines share the same transformation and the same material properties they can be combined into large vertex and index buffers. The overhead of scenegraph traversing and handling is reduced massively. – vre Aug 25 '22 at 20:05
  • BTW, a purely stylistic issue: You can remove all the stars from the `auto` declarations. They are not needed, as the return type of the RHS expressions is always pointer to type. – vre Aug 25 '22 at 20:13