Low FPS for transforming surfaces

We want to transform 3D models in ‘real time’ in 3D Slicer with data coming from outside. The problem is, that the frame rate we currently reach is very low. Below is some demonstration code to show our approach:

hpp:

QTimer fpsTestUpdateTimer;
vtkSmartPointer<vtkTransform> fpsTestTransform;
vtkMRMLTransformNode* fpsTestTransformNode;

cpp:

{
    ...
    connect(&fpsTestUpdateTimer, &QTimer::timeout, this, &MovementDemo::fpsTestTimerCallback);
    ...
}

//----------------------------------------------------------------------------
void MovementDemo::startStopFpsTest(bool enable, int fps)
{
    if (!enable)
    {
        fpsTestUpdateTimer.stop();
    }
    else if(!trackingUpdateTimer.isActive())
    {
        qSlicerApplication* app = qSlicerApplication::application();
        fpsTestTransform = vtkSmartPointer<vtkTransform>::New();
        fpsTestTransform->RotateZ(1);
        fpsTestTransformNode = vtkMRMLTransformNode::SafeDownCast(app->mrmlScene()->GetFirstNodeByName("FpsTestTransform"));
        fpsTestUpdateTimer.start(1000 / fps);
    }
}

//----------------------------------------------------------------------------
void MovementDemo::fpsTestTimerCallback()
{
    fpsTestTransformNode->ApplyTransform(fpsTestTransform);
}

The transform is applied to a model with about 1600 vertices.
On a screen with a resolution of 1920x1080, where the 3D view occupies about half of the screen space, we get about 10-20 fps (according to the fps display of the 3D viewer).
When I activate the ‘3D view rock’ image with the same scene contents, we easily reach 60 fps, even on a 4K screen (again the 3D view occupying half of the screen space).

Here are two videos for illustration (and because of the season):
Change transform:
v15

Rock animation:
v60

Any help regarding optimization possibilities is greatly appreciated.
Happy holidays!

ApplyTransform modifies the polydata in each iteration, which is time-consuming and in the long term even some error in the point coordinates may accumulate.

If you want to rotate the viewpoint then modify the camera position (this is done when you activate 3D view rock feature). If you want to modify an object pose then set its parent transform once, then modify the matrix in the transform node at each iteration.

Thank you Andras, I adapted my code to the following:

//----------------------------------------------------------------------------
void MovementDemo::fpsTest(bool enable, int fps)
{
    if (!enable)
    {
        fpsTestUpdateTimer.stop();
    }
    else if(!trackingUpdateTimer.isActive())
    {
        qSlicerApplication* app = qSlicerApplication::application();

        fpsTestUpdateTimer.start(1000 / fps);
        fpsTestTransformNode = vtkMRMLLinearTransformNode::SafeDownCast(app->mrmlScene()->GetFirstNodeByName("FpsTestTransform"));
        fpsTestTransform = vtkTransform::SafeDownCast(fpsTestTransformNode->GetTransformToParent());
    }
}

//----------------------------------------------------------------------------
void MovementDemo::fpsTestTimerCallback()
{
    fpsTestTransform->RotateZ(1);
    fpsTestTransformNode->Modified();
}

The results are better - some data:

fps setting (= 1000 / timer callback rate) --> displayed fps
30 --> 20
40 --> 30
50 --> 30
60 --> 30
80 --> 33
100 --> 35
120 --> 33
200 --> 40
300 --> 40
500 --> 40

I don’t quite understand the connection between my timer repetition rate and the resulting frame rate. Can it be connected with vsync? Just for information: the CPU load during the test (fps setting 120) is around 5%, the GPU load at around 10%.

I really would like to reach a refresh rate of 60Hz for this simple example. Are there any more suggestions for what I could try to do? Would it help to call ‘forceRender’ like it is done in the rock animation? https://github.com/commontk/CTK/blob/78341abaf1c56bbdcc70372ffefed91e2a940d32/Libs/Visualization/VTK/Widgets/ctkVTKRenderView.cpp#L190

vsync caps the refresh rate at 60Hz (probably on a simple rendering you could do hundreds of fps).

scheduleRender mechanism in ctkVTKAbstractView optimizes number of actual renderings in response to scheduleRender requests. By default refresh rate is capped at 60Hz and up to 1 frame latency is introduced, but it is a small price to pay considering that it eliminates unnecessary re-renderings.

For replaying animations at a fixed frame rate you may circumvent scheduleRender mechanism and issue forceRender calls at regular time intervals. This should not be a problem as long as there is only one component that does this (if you had multiple sources of forceRender then your application could slow down due to many unnecessary re-renderings).

just to share my experiences here:

scheduleRender clearly would be the desired solution, however, I never managed to reach a frame rate > 40 (according to the fps display of the 3d view), when calling scheduleRender 80-500 times a second with an empty scene and with the window sized such that the 3d view has 300x400px. As said, the rock animation renders with 60 fps.

One possible reason you do not reach the maximum target refresh rate (60fps by default) is that transforming a node is a costly operation (in contrast what “3D view rock” does - just rotating the camera). It may also trigger update of multiple views, which may further slow down the rendering. You can build in RelWithDebInfo and run the code with a profiler to get definitive answer.

Oh, I forgot to mention, I commented out any transform manipulation in my last test - so only the following code reaches a maximum of 40 fps:

void MovementDemo::fpsTestTimerCallback()
{
    if (!animationIntervalMs)
    {
        return;
    }

    threeDView->scheduleRender();

    QTimer::singleShot(animationIntervalMs, this, SLOT(fpsTestTimerCallback()));
}

When I use forceRender, the 60 fps can be reached.
I am currently thinking about updating the view constantly with 60 Hz using forceRender and performing the updates of the transforms with another timer.