Is this video performance normal or am I doing something wrong?

Hello. I’m developing a generative art animation and after a few seconds it starts reducing the FPS. I want to know if the problem is that I’m doing something wrong, I mean in a non efficient way. Or if this animation would normally be laggy due to its nature…
here’s the code and a video example:
output

void ofApp::setup()
{
    ofEnableAlphaBlending();
    ofBackground(0);
    //ofSetVerticalSync(false);
   // ofSetFrameRate(1000);
    ofSetCircleResolution(20);
    camera.setPosition(w / 2, h / 2, -500);
    camera.lookAt({w / 2, h / 2, 0});

    ofNoFill();
    ofSetLineWidth(1.5);
}

//--------------------------------------------------------------
void ofApp::update()
{
    camera.dolly(-0.2);
    camera.rollDeg(0.05);

    //  creating lines
    float static time;
    if (ofGetElapsedTimef() - time > ofRandom(.5, 2) && lines.size() < 30)
    {
        time = ofGetElapsedTimef();
        lines.push_back(ofPolyline());
        scales.push_back(1);
        rotations.push_back(0);
        alphas.push_back(150);
        scalar.push_back(ofRandom(.3, .7));
        c++;
        counter.push_back(c);

        // possible endpoints
        glm::vec3 endpoints[4] = {
            {ofRandomWidth(), h + 0, camera.getZ() + 300},  // BOTTOM
            {ofRandomWidth(), -0, camera.getZ() + 300},     // TOP
            {w + 0, ofRandomHeight(), camera.getZ() + 300}, // RIGHT
            {-0, ofRandomHeight(), camera.getZ() + 300}     // LEFT
        };

        int start_index = floor(ofRandom(0, 4));
        int end_index;
        int r = rand() % 100;
        if (start_index == 0 || start_index == 1)
        {
            if (r < 25)
            {
                end_index = floor(ofRandom(2, 4));
            }
            else
            {
                end_index = (start_index + 1) % 2;
            }
        }
        else
        {
            if (r < 25)
            {
                end_index = floor(ofRandom(0, 2));
            }
            else
            {
                end_index = (start_index - 1) % 2 + 2;
            }
        }

        // creating first and last vertices positions
        emitter.push_back(endpoints[start_index]);
        end.push_back(endpoints[end_index]);

        lines.back().addVertex(emitter.back());
    }

    // loop every line and apply changes
    for (int i = 0; i < (int)lines.size(); i++)
    {
        lines[i].addVertex({emitter[i]});
        glm::vec3 direction = glm::normalize(end[i] - emitter[i]);
        glm::vec3 normal = perp2D(direction);
        glm::vec3 noise = normal * ofSignedNoise(counter[i] * 1000, ofGetElapsedTimef());
        float length = 1;
        emitter[i] += direction * length + noise;
        rotations[i] += .05 * scalar[i];
        scales[i] += 0.0003;
        if (end[i].z < camera.getZ() + 100)
        {
            alphas[i] -= 1;
        }

        if (alphas[i] <= 0)
        {
            lines.erase(lines.begin());
            scales.erase(scales.begin());
            alphas.erase(alphas.begin());
            emitter.erase(emitter.begin());
            end.erase(end.begin());
            rotations.erase(rotations.begin());
            scalar.erase(scalar.begin());
        }
    }

    showFps();

    // recorder.record("/home/ezequiel/Videos/flowfield", 18, 1);
}

//--------------------------------------------------------------
void ofApp::draw()
{
    for (int j = 0; j < (int)lines.size(); j++)
    {
        // draw normals
        float width = 7.5;
        float density = .65 * lines[j].getPerimeter();
        for (int i = 0; i < density; i++)
        {
            float length = width - width * i / density;
            glm::vec3 point = lines[j].getPointAtPercent(i / density);
            glm::vec3 pt_screen = camera.worldToScreen(point);
            if (pt_screen.x > -50 && pt_screen.x < w + 50)
            {
                float index = lines[j].getIndexAtPercent(i / density);
                glm::vec3 normal = (lines[j].getNormalAtIndexInterpolated(index));
                normal = glm::normalize(normal) * length;

                camera.begin();
                ofSetColor(ofColor::white, alphas[j]);
                ofDrawCircle(point.x, point.y, point.z, length);
                camera.end();
            }
        }
    }
}

Thank you

Hey @Ezequiel_Leon_Zybert , looks like a fun project! Here are a few things that might help improve the fps.

Maybe try calling camera.begin() just once at the beginning of ofApp::draw(), and camera.end() after all of the drawing is done.

All of the things that are needed for a “generative line” could come together in a class (or struct) called Line. A class Line could inherit from ofPolyline. Instances of the class can be stored in a std::vector<Line> lines, and erased with just 1 call instead of multiple calls for multiple vectors. Each Line instance would have its own state and everything it needs to be a Line, including the ability to update and draw itself. ofBook has an great chapter on writing custom classes.

Elements in a vector can be replaced, rather than using erase and/or push_back. An int n can hold an index value for the newest element in the vector, with n+1 being the oldest. n is incremented when the oldest member is replaced, and n resets to zero as needed. So an index strategy might improve the fps by eliminating all of the calls to std::vector<>::erase().

Edit: also just make sure you don’t have any vectors that grow indefinitely, or get super huge. Really big vectors can slow things down for a variety of reasons.

1 Like

Nice tips from @TimChi .
I would try to count the lines, and the vertex per line to see if some lines are growing in vertices and not being eliminated.

Thanks! I will try with this tips

dimitre’s suggestion is great, particularly for the size of the vertices vector in each ofPolyline in lines. It might be easy to look at a total in real time:

int sum = 0;
for(const auto& line : lines{
    sum += line.getVertices().size();
}
ofDrawBitmapString("sum: " + ofToString(sum), 20.f, 20.f);

If the ofPolylines are getting too big, you could try replacing the older vertices values with new ones, as the older ones might be beyond the view of the camera anyway. Again, using a replacement strategy might be way more efficent than .erase() of the first element.

Yes I use the same. In some particles system I even use fixed size arrays to be very strict about memory, and I use a cursor, a variable to indicate where I can write new data on the array, or where to begin or end the line. something similar to a ring buffer / circular buffer.
the good thing about this approach is you can easily copy the entire object if needed, maybe for multithreading.

1 Like