[SOLVED] C++ code resulting in skipped frames

Hi all,
I am trying out Panda3d/C++ and at the moment working through the Solar System sample, converting Python code to C++ tutorial by tutorial. All works well except that the planets revolving around Sun don’t do so at a fixed angular velocity (only observation) and that the animation jumps a few frames every few seconds. The Python code runs flawlessly. I even tried limiting the framerate but no resolve. I even tried C++ code from drunken-octo-robot’s repository and I see similar issue. I couldn’t find anything to that effect searching the forums and online so I decided to post my query here.
Thanks

solarsystem.h

  #ifndef SOLARSYSTEM_H
  #define SOLARSYSTEM_H

  #include "pandaFramework.h"
  #include "pandaSystem.h"
  #include "texture.h"
  #include "texturePool.h"
  #include "cLerpNodePathInterval.h"
  #include "lvecBase3.h"
  #include "cIntervalManager.h"
  #include "cMetaInterval.h"
  #include "asyncTaskManager.h"
  #include "clockObject.h"
  #include <iostream>

  class solarsystem
  {
  private:
      PandaFramework framework;
      WindowFramework* window;
      float sizeScale = 0.6f;
      float orbitScale = 10.0f;
      float yearScale = 60.0f;
      float dayScale = yearScale / 356.0f * 5.0f;
      NodePath sky, sun, mercury, venus, earth, mars, moon;
      NodePath orbit_root_mercury, orbit_root_venus, orbit_root_earth, orbit_root_mars, orbit_root_moon;
      CLerpNodePathInterval *day_period_sun, *day_period_mercury, *day_period_venus, *day_period_earth, *day_period_mars, *day_period_moon;
      CLerpNodePathInterval *orbit_period_mercury, *orbit_period_venus, *orbit_period_earth, *orbit_period_mars, *orbit_period_moon;
      Texture* sky_tex, *sun_tex, *mercury_tex, *venus_tex, *earth_tex, *mars_tex, *moon_tex;


  public:
      solarsystem(int argc, char* args[]);
      double timer;
      void loadPlanets();
      void rotatePlanets();
      static AsyncTask::DoneStatus stepIntervalManager(GenericAsyncTask* taskPtr,void* dataPtr);
      void run();
  };

  #endif // SOLARSYSTEM_H

solarsystem.cpp

#include "solarsystem.h"
using namespace std;
solarsystem::solarsystem(int argc, char* args[])
{
    framework.open_framework(argc,args);
    framework.set_window_title("Hello world");

    window = framework.open_window();
    TextNode * text = new TextNode("title");
    text->set_text("Panda3D: Tutorial 1 - Solar System");

    NodePath textNodePath = window->get_aspect_2d().attach_new_node(text);
    textNodePath.set_pos(0,0.8,-0.95);
    textNodePath.set_scale(0.07);

    NodePath camera = window->get_camera_group();
    camera.set_pos(0,0,45);
    camera.set_hpr(0,-90,0);

    timer = AsyncTaskManager::get_global_ptr()->get_clock()->get_frame_time();

}
void solarsystem::loadPlanets()
{
    // /opt/panda3d/samples/Solar-System/models
    orbit_root_mercury = window->get_render().attach_new_node("orbit_root_mercury");
    orbit_root_venus = window->get_render().attach_new_node("orbit_root_venus");
    orbit_root_earth = window->get_render().attach_new_node("orbit_root_earth");
    orbit_root_moon = orbit_root_earth.attach_new_node("orbit_root_moon");
    orbit_root_mars = window->get_render().attach_new_node("orbit_root_mars");

    sky = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/solar_sky_sphere");
    sky.reparent_to(window->get_render());
    sky.set_scale(40);

    sky_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/stars_1k_tex.jpg");
    sky.set_texture(sky_tex,1);

    sun = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    sun.reparent_to(window->get_render());
    sun.set_scale(2 * sizeScale);

    sun_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/sun_1k_tex.jpg");
    sun.set_texture(sun_tex,1);

    mercury = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    mercury_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/mercury_1k_tex.jpg");
    mercury.set_texture(mercury_tex,1);
    mercury.reparent_to(orbit_root_mercury);
    mercury.set_pos(0.38 * orbitScale,0,0);
    mercury.set_scale(0.385 * sizeScale);

    venus = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    venus_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/venus_1k_tex.jpg");
    venus.set_texture(venus_tex,1);
    venus.reparent_to(orbit_root_venus);
    venus.set_pos(0.72 * orbitScale,0,0);
    venus.set_scale(0.923 * sizeScale);

    earth = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    earth_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/earth_1k_tex.jpg");
    earth.set_texture(earth_tex,1);
    earth.reparent_to(orbit_root_earth);
    earth.set_pos(orbitScale,0,0);
    earth.set_scale(sizeScale);

    mars = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    mars_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/mars_1k_tex.jpg");
    mars.set_texture(mars_tex,1);
    mars.reparent_to(orbit_root_mars);
    mars.set_pos(1.52 * orbitScale,0,0);
    mars.set_scale(0.515 * sizeScale);

    orbit_root_moon.set_pos(orbitScale,0,0);

    moon = window->load_model(framework.get_models(),"/opt/panda3d/samples/Solar-System/models/planet_sphere");
    moon_tex = TexturePool::load_texture("/opt/panda3d/samples/Solar-System/models/moon_1k_tex.jpg");
    moon.set_texture(moon_tex,1);
    moon.reparent_to(orbit_root_moon);
    moon.set_pos(0.1*orbitScale,0,0);
    moon.set_scale(0.1 * sizeScale);
}

void solarsystem::rotatePlanets()
{
    LPoint3f rotVecStart(0,0,0);
    LPoint3f rotVecEnd(360,0,0.0);
    NodePath render = window->get_render();
    day_period_sun = new CLerpNodePathInterval("day_period_sun",20,CLerpInterval::BT_no_blend,false, true,sun,render);
    day_period_sun->set_start_hpr(rotVecStart);
    day_period_sun->set_end_hpr(rotVecEnd);

    orbit_period_mercury = new CLerpNodePathInterval("orbit_period_mercury",0.241*yearScale,CLerpInterval::BT_no_blend,true, false,orbit_root_mercury,render);
    orbit_period_mercury->set_start_hpr(rotVecStart);
    orbit_period_mercury->set_end_hpr(rotVecEnd);

    orbit_period_venus = new CLerpNodePathInterval("orbit_period_venus",0.615*yearScale,CLerpInterval::BT_no_blend,true, false,orbit_root_venus,render);
    orbit_period_venus->set_start_hpr(rotVecStart);
    orbit_period_venus->set_end_hpr(rotVecEnd);

    orbit_period_earth = new CLerpNodePathInterval("orbit_period_earth",yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_earth,render);
    orbit_period_earth->set_start_hpr(rotVecStart);
    orbit_period_earth->set_end_hpr(rotVecEnd);

    orbit_period_mars = new CLerpNodePathInterval("orbit_period_mars",1.881*yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_mars,render);
    orbit_period_mars->set_start_hpr(rotVecStart);
    orbit_period_mars->set_end_hpr(rotVecEnd);

    orbit_period_moon = new CLerpNodePathInterval("orbit_period_moon",0.0749*yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_moon,render);
    orbit_period_moon->set_start_hpr(rotVecStart);
    orbit_period_moon->set_end_hpr(rotVecEnd);

    day_period_sun->loop();
    orbit_period_mercury->loop();
    orbit_period_venus->loop();
    orbit_period_earth->loop();
    orbit_period_mars->loop();
    orbit_period_moon->loop();

    AsyncTaskManager::get_global_ptr()->add(
          new GenericAsyncTask("intervalManagerTask", stepIntervalManager, this));
}

AsyncTask::DoneStatus solarsystem::stepIntervalManager(GenericAsyncTask* taskPtr,
                                                       void* dataPtr)
{
    double now = AsyncTaskManager::get_global_ptr()->get_clock()->get_frame_time();
    if(now - ((solarsystem*)dataPtr)->timer > 1.0/30.0 )
    {
        //cout << now << endl;
        CIntervalManager::get_global_ptr()->step();
        ((solarsystem*)dataPtr)->timer = now;
    }
    return AsyncTask::DS_cont;
}

void solarsystem::run()
{
    framework.main_loop();
    framework.close_framework();
}

int main(int argc, char* args[])
{
    solarsystem s(argc,args);
    s.loadPlanets();
    s.rotatePlanets();
    s.run();
}

Well, you have code in stepIntervalManager that specifically induces chugging by not calling the interval manager every frame, so have you tried taking out that if check in there?

Thanks rdb. Since stepIntervalManager is being registered as a GenericAsyncTask, would it not already be called once every frame?

AsyncTask::DoneStatus solarsystem::stepIntervalManager(GenericAsyncTask* taskPtr,
                                                       void* dataPtr)
{
    double now = AsyncTaskManager::get_global_ptr()->get_clock()->get_frame_time();
    if(now - ((solarsystem*)dataPtr)->timer > 1.0/30.0 )
    {
        //cout << now << endl;
        CIntervalManager::get_global_ptr()->step();
        ((solarsystem*)dataPtr)->timer = now;
    }
    return AsyncTask::DS_cont;
}

From observing the resulting animation, it feels like the animation is faster when it starts, slows down, then skips few frames, and repeats. Number of frames skipped is different every time.

But what purpose does the timer mechanism serve? It looks to me like it would limit the number of times the interval manager is stepped, and hence introduce chugginess.

Removing the timer has similar effects

    AsyncTask::DoneStatus solarsystem::stepIntervalManager(GenericAsyncTask* taskPtr,
                                                           void* dataPtr)
    {
        //double now = AsyncTaskManager::get_global_ptr()->get_clock()->get_frame_time();
        //if(now - ((solarsystem*)dataPtr)->timer > 1.0/30.0 )
        //{
            //cout << now << endl;
            CIntervalManager::get_global_ptr()->step();
            //((solarsystem*)dataPtr)->timer = now;
        //}
        return AsyncTask::DS_cont;
    }

Nothing in the C++ infrastructure should make intervals behave differently than in the Python infrastructure (it’s the same underlying code, after all). Intervals are automatically time-corrected based on the achieved frame rate.

From what you describe, it sounds like the observed frames are not displayed at the rate that Panda believes they are being displayed. I’ve seen that happen occasionally in unusual circumstances, for instance because a driver will hold up frames and then display them all at once, or because the system clock is not reporting accurate timing information.

Still, if either of these weird things is happening, you’d expect it also to happen in Python, so I don’t have any ideas why your C++ code is behaving differently. You could try playing with some of the following Config.prc settings:

sync-video 1
paranoid-clock 1
lock-to-one-cpu 1

David

Thanks for your recommendations drwr. The configuration change didn’t fix it for me either.
However I did notice that the animation was smoothened when I added an I/O task in the stepIntervalManager.
I was curious so I added the following in the stepIntervalManager.

cout << AsyncTaskManager::get_global_ptr()->get_clock()->get_net_frame_rate() << endl;

I immediately noticed animation getting smooth.
I replaced that code with Thread.sleep() and was able to achieve similar results for values higher than 0.0001 seconds.
Lower second values resulted in choppiness as described earlier but not as much.

Thread::get_current_thread()->sleep(0.0001);

I also noticed that after I completely converted the Tutorial 5 of Solar System to C++, with more CLerpNodePathInterval::loop() calls, without the Thread::sleep() the choppiness reduced but was still there.
Adding sleep for 0.0001 seconds smooths out the animation just enough to where it is either not noticable or not present at all.

Seems like putting the current thread to sleep at each frame fixes my issue.
Any thoughts?

Here is the complete code for your reference:
solarsystem.cpp

#include "solarsystem.h"
using namespace std;
solarsystem::solarsystem(PandaFramework* framework)
{
    this->framework = framework;
    framework->set_window_title("Hello world");

    window = framework->open_window();
    TextNode * text = new TextNode("title");
    text->set_text("Panda3D: Tutorial 1 - Solar System");

    NodePath textNodePath = window->get_aspect_2d().attach_new_node(text);
    textNodePath.set_pos(0,0.8,-0.95);
    textNodePath.set_scale(0.07);

    NodePath camera = window->get_camera_group();
    camera.set_pos(0,0,45);
    camera.set_hpr(0,-90,0);
}
void solarsystem::loadPlanets()
{
    // /usr/share/panda3d/samples/Solar-System/models
    orbit_root_mercury = window->get_render().attach_new_node("orbit_root_mercury");
    orbit_root_venus = window->get_render().attach_new_node("orbit_root_venus");
    orbit_root_earth = window->get_render().attach_new_node("orbit_root_earth");
    orbit_root_moon = orbit_root_earth.attach_new_node("orbit_root_moon");
    orbit_root_mars = window->get_render().attach_new_node("orbit_root_mars");

    sky = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/solar_sky_sphere");
    sky.reparent_to(window->get_render());
    sky.set_scale(40);

    sky_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/stars_1k_tex.jpg");
    sky.set_texture(sky_tex,1);

    sun = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    sun.reparent_to(window->get_render());
    sun.set_scale(2 * sizeScale);

    sun_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/sun_1k_tex.jpg");
    sun.set_texture(sun_tex,1);

    mercury = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    mercury_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/mercury_1k_tex.jpg");
    mercury.set_texture(mercury_tex,1);
    mercury.reparent_to(orbit_root_mercury);
    mercury.set_pos(0.38 * orbitScale,0,0);
    mercury.set_scale(0.385 * sizeScale);

    venus = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    venus_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/venus_1k_tex.jpg");
    venus.set_texture(venus_tex,1);
    venus.reparent_to(orbit_root_venus);
    venus.set_pos(0.72 * orbitScale,0,0);
    venus.set_scale(0.923 * sizeScale);

    earth = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    earth_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/earth_1k_tex.jpg");
    earth.set_texture(earth_tex,1);
    earth.reparent_to(orbit_root_earth);
    earth.set_pos(orbitScale,0,0);
    earth.set_scale(sizeScale);

    mars = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    mars_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/mars_1k_tex.jpg");
    mars.set_texture(mars_tex,1);
    mars.reparent_to(orbit_root_mars);
    mars.set_pos(1.52 * orbitScale,0,0);
    mars.set_scale(0.515 * sizeScale);

    orbit_root_moon.set_pos(orbitScale,0,0);

    moon = window->load_model(framework->get_models(),"/usr/share/panda3d/samples/Solar-System/models/planet_sphere");
    moon_tex = TexturePool::load_texture("/usr/share/panda3d/samples/Solar-System/models/moon_1k_tex.jpg");
    moon.set_texture(moon_tex,1);
    moon.reparent_to(orbit_root_moon);
    moon.set_pos(0.1*orbitScale,0,0);
    moon.set_scale(0.1 * sizeScale);
}

void solarsystem::rotatePlanets()
{
    LPoint3f rotVecStart(0,0,0);
    LPoint3f rotVecEnd(360,0,0.0);
    NodePath render = window->get_render();
    day_period_sun = new CLerpNodePathInterval("day_period_sun",20,CLerpInterval::BT_no_blend,false, true,sun,render);
    day_period_sun->set_start_hpr(rotVecStart);
    day_period_sun->set_end_hpr(rotVecEnd);

    orbit_period_mercury = new CLerpNodePathInterval("orbit_period_mercury",0.241*yearScale,CLerpInterval::BT_no_blend,true, false,orbit_root_mercury,render);
    orbit_period_mercury->set_start_hpr(rotVecStart);
    orbit_period_mercury->set_end_hpr(rotVecEnd);

    orbit_period_venus = new CLerpNodePathInterval("orbit_period_venus",0.615*yearScale,CLerpInterval::BT_no_blend,true, false,orbit_root_venus,render);
    orbit_period_venus->set_start_hpr(rotVecStart);
    orbit_period_venus->set_end_hpr(rotVecEnd);

    orbit_period_earth = new CLerpNodePathInterval("orbit_period_earth",yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_earth,render);
    orbit_period_earth->set_start_hpr(rotVecStart);
    orbit_period_earth->set_end_hpr(rotVecEnd);

    orbit_period_mars = new CLerpNodePathInterval("orbit_period_mars",1.881*yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_mars,render);
    orbit_period_mars->set_start_hpr(rotVecStart);
    orbit_period_mars->set_end_hpr(rotVecEnd);

    orbit_period_moon = new CLerpNodePathInterval("orbit_period_moon",0.0749*yearScale,CLerpInterval::BT_no_blend,false, false,orbit_root_moon,render);
    orbit_period_moon->set_start_hpr(rotVecStart);
    orbit_period_moon->set_end_hpr(rotVecEnd);

    day_period_mercury = new CLerpNodePathInterval("day_period_mercury",59*dayScale,CLerpInterval::BT_no_blend,true, false,mercury,render);
    day_period_mercury->set_start_hpr(rotVecStart);
    day_period_mercury->set_end_hpr(rotVecEnd);

    day_period_venus = new CLerpNodePathInterval("day_period_venus",243*dayScale,CLerpInterval::BT_no_blend,true, false,venus,render);
    day_period_venus->set_start_hpr(rotVecStart);
    day_period_venus->set_end_hpr(rotVecEnd);

    day_period_earth = new CLerpNodePathInterval("day_period_earth",dayScale,CLerpInterval::BT_no_blend,false, false,earth,render);
    day_period_earth->set_start_hpr(rotVecStart);
    day_period_earth->set_end_hpr(rotVecEnd);

    day_period_mars = new CLerpNodePathInterval("day_period_mars",1.03*dayScale,CLerpInterval::BT_no_blend,false, false,mars,render);
    day_period_mars->set_start_hpr(rotVecStart);
    day_period_mars->set_end_hpr(rotVecEnd);

    day_period_moon = new CLerpNodePathInterval("day_period_moon",0.0749*yearScale,CLerpInterval::BT_no_blend,false, false,moon,render);
    day_period_moon->set_start_hpr(rotVecStart);
    day_period_moon->set_end_hpr(rotVecEnd);

    day_period_sun->loop();
    orbit_period_mercury->loop();
    orbit_period_venus->loop();
    orbit_period_earth->loop();
    orbit_period_mars->loop();
    orbit_period_moon->loop();
    day_period_mercury->loop();
    day_period_venus->loop();
    day_period_earth->loop();
    day_period_mars->loop();
    day_period_moon->loop();
}

AsyncTask::DoneStatus solarsystem::stepIntervalManager(GenericAsyncTask* taskPtr,
                                                       void* dataPtr)
{
    CIntervalManager::get_global_ptr()->step();
    Thread::get_current_thread()->sleep(0.0001);
    return AsyncTask::DS_cont;
}

void solarsystem::run()
{
    AsyncTaskManager::get_global_ptr()->add(
         new GenericAsyncTask("intervalManagerTask", stepIntervalManager, this));
}

int main(int argc, char* args[])
{
    PandaFramework framework;
    framework.open_framework(argc,args);
    solarsystem s(&framework);
    s.loadPlanets();
    s.rotatePlanets();
    s.run();
    framework.main_loop();
    framework.close_framework();
}

That’s a driver issue I’ve seen before. What happens is, if you never sleep the CPU, then your main thread uses 100% of the available CPU, and it starves out the actual graphics driver, which is forced to run only a few times a second. When it does get to run, it quickly renders all of the frames that accumulated since the last time it ran.

It’s usually a good idea to let the CPU sleep each frame anyway; and the Python task loop does this by default. I forgot that the C++ framework doesn’t set this up by default.

David

Thanks for the explanation. It makes sense.
What’s the sleep duration set in Python?
Will this be incorporated in future versions of Panda3d/C++?

Hmm, looking at the code now, I don’t see a default sleep in the Python code after all–the default is set to 0. Strange.

Are you running on Linux? Normally, the “sync-video 1” setting also solves the problem, because it forces the CPU to wait each frame for the next video refresh rate. But some Linux drivers seem to ignore this setting.

David

Yes. Running Linux. sync-video 1 didn’t help though. Also, Python examples work flawlessly. Issue only seems to be with C++. Besides Hello World, I’ve only tested Solar System code so far.

Hmm, it might be just that the tiny amount of Python overhead is enough to slow down rendering sufficiently to give the driver a chance to keep up.

David