Just to give you an idea what’s wrong with VCVRack

May 1, 2018

Here is a windowRun() function which is an event handling/rendering loop. It runs at VSync rate (if it's supported/enabled or at 90 FPS otherwise). Let's assume it's 60 FPS. Each frame, cursorPosCallback() is called. That functions doesn't check whether the cursor position has actually changed or not. It does number of things but we're now interested in this line where onDragMove() is called for a gDraggedWidget if it's not NULL. gDraggedWidget is set here whenever you press left mouse button over a widget. Suppose, you pressed it over a module background (not a control), then requestModuleBoxNearest() will be called here to find a new position for the module so that it doesn't overlap other modules.

So far we see that some function is being called even when it's not really neccessary - that's bad but not the end of the world, right? Then now on to the interesting part. I'll provide full code of this function here:

bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) {
    // Create possible positions
    int x0 = roundf(box.pos.x / RACK_GRID_WIDTH);
    int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT);
    std::vector positions;
    for (int y = max(0, y0 - 8); y < y0 + 8; y++) {
        for (int x = max(0, x0 - 400); x < x0 + 400; x++) {
            positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT));
        }
    }

    // Sort possible positions by distance to the requested position
    std::sort(positions.begin(), positions.end(), [box](Vec a, Vec b) {
        return a.minus(box.pos).norm() < b.minus(box.pos).norm();
    });

    // Find a position that does not collide
    for (Vec position : positions) {
        Rect newBox = box;
        newBox.pos = position;
        if (requestModuleBox(m, newBox))
            return true;
    }
    return false;
}

Isn't this wonderful? Create an array of 12800 vectors, then sort it, computing more than 100k square roots (not counting other operations), then iterate over these vectors again until we find a non-overlapping position (requestModuleBox() will iterate over all the modules too each time) - and all this at 60 FPS when you just pressed a mouse button and didn not even move the cursor (and of course it can be done much faster and simpler when you do move)!

Of course, this is not the only place with such... um... coding style. On the bright side, now I know why modules are moving not quite as smooth as desired (on Tinker Board this mess drops FPS to less than 20 when dragging a module).

And even better news is that the overall low FPS I was experiencing on Tinker Board can be fixed by just using full-screen window. There's a bug in GPU driver, Xorg or somewhere. Anyway, I'll just default to fullscreen on ARM (Raspberry Pi doesn't have this problem but it won't hurt anyway) and won't have to work on running without an X server for now.


miRack - an optimised fork of VCVRack primarily targeting Raspberry Pi, ASUS Tinker Board and similar hardware. But will keep your desktop cooler too.