Two SceneManagers from two threads - rendering problems Topic is solved

Problems building or running the engine, queries about how to use features etc.
peci1
Gnoblar
Posts: 2
Joined: Sun Aug 10, 2025 2:59 pm

Two SceneManagers from two threads - rendering problems

Post by peci1 »

Ogre Version: 1.9.1
Operating System: Ubuntu 20.04
Render System: GLX

Hi, I'm still developing something for ROS Noetic, which runs on 20.04 and OGRE 1.9.1, so these things can't be changed (for now, later I'll also make it compatible with newer releases).

Basically, I develop nodelets (ROS terminology), which are plugin-based tasks which can be loaded into a master process (nodelet manager). The nodelet manager has a pool of threads and each registered nodelet runs its incoming data callbacks on these threads. The nodelet manager is a super simple and super general thing that can't be changed and it knows nothing about OGRE, OpenGL or whatever. It just registers the nodelets and runs their callbacks, whatever they do.

The nodelets I develop utilize OGRE to do some rendering to RTTs, which I then read out into CPU memory and send them further as a ROS Image message. So no rendering to screen, at any time.

The problem is that as long as there is only one instance of the nodelet loaded, it works perfectly, but loading any other instance results in weird behavior of the renderers.

I know that using OGRE from multiple threads is difficult, yet I have no other choice (the nodelets cannot choose on which thread their callback will be called). So my general strategy is that the first loaded nodelet sets up all the static infrastructure needed for OGRE, and the other nodelets just reuse it. When it comes to rendering, there is a static mutex locked by each nodelet, so that there are no concurrency problems during the rendering phase itself. I.e., only one rendering call at a time.

Each nodelet creates its own SceneManager, its own RTT texture, its own scene, its own compositors etc. The only things I could not separate are the singletons like Root, RenderSystem etc.

Code: Select all


// The setup() and render() calls for each nodelet both run in a single thread, and the thread is different for each nodelet

void setup()
{
  // Here the code checks if Ogre::Root::getSingletonPtr() is null. If it is, it
  // does the required OGRE setup. If it is non-null, no setup is done, just
  // root->getRenderSystem()->registerThread();

  // A separate scene_manager_ for each nodelet / thread
  scene_manager_ = root->createSceneManager(Ogre::ST_GENERIC);
  default_light_ = scene_manager_->createLight("MainLight");
  // some point light setup
  scene_manager_->setAmbientLight(Ogre::ColourValue(.5, .5, .5));
  scene_node_ = scene_manager_->getRootSceneNode()->createChildSceneNode();
  // setup the scene
  camera_ = scene_manager_->createCamera("RobotModelCamera");
  // setup camera

  // Create the RTT
  tex_ = root->getTextureManager()->createManual(
    "MainRenderTarget", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D,
    res.width, res.height, 32, 0, this->config.pixelFormat, Ogre::TU_RENDERTARGET);
  rt_ = tex_->getBuffer()->getRenderTarget();

  viewPort_ = rt_->addViewport(camera_);
  viewPort_->setClearEveryFrame(true);

  // I add some compositors here to viewPort_, but the problems are even with no compositors
}

void render()
{
  static std::mutex renderingMutex;
  std::lock_guard<std::mutex> lock(renderingMutex);

  rt_->update();
  
cv::Mat rectImg(rectRows, rectCols, this->cvImageType); const Ogre::PixelBox pb(rt_->getWidth(), rt_->getHeight(), 1, this->config.pixelFormat, rectImg.data); rt_->copyContentsToMemory(pb); }

I face several problems:

  1. Even with no compositor, only the first RTT is rendered correctly. The other RTTs seem like they have somehow weirdly set up lighting (as if the only point light I have was looking at the scene from a wrong point, and the ambient light did nothing).

  2. When I add compositors, they also behave weird. I created the simplest possible compositor just to test this:

Code: Select all

Test.compositor:

compositor Test
{
  technique
  {
    target_output
    {
      input previous

  pass render_quad
  {
    material TestMat
  }
}
  }
}

TestMat.material:

fragment_program TestFS glsl
{
  source test_fs.glsl
}

material TestMat
{
  technique
  {
    pass
    {
      fragment_program_ref TestFS { }
    }
  }
}

test_fs.glsl:

void main()
{
  gl_FragColor.r = 1.0;
  gl_FragColor.g = 0.0;
  gl_FragColor.b = 1.0;
  gl_FragColor.a = 1.0;
}

This compositor added to the first RTT works correctly and results in a full pink image. On the second instance of RTT, I get only the wrongly lit model as described in issue 1. If I change "input previous" to "input none", I get a 0,0,0,0 image.

However, if I change "render_quad" to "render_scene", the second RTT again contains the same wrongly lit model (or, with a clear pass, I get a correctly cleared output). So the compositor itself works, it is just the render_quad that works wrong (and the rendering of the scene, as described in issue 1).

Here are a few images. These are the RTT contents of the very same scene, with identical scene setup, lighting, materials etc:

First RTT:

Image

Any other RTT:

Image

I also created a simplified material to just render everything in red color:

Code: Select all

color_material_ = Ogre::MaterialPtr(new Ogre::Material(
        nullptr, material_name, 0, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME));
color_material_->setReceiveShadows(false);
color_material_->getTechnique(0)->setLightingEnabled(true);
Ogre::ColourValue color = color_material_->getTechnique(0)->getPass(0)->getDiffuse();
color.r = 1;
color.g = 0;
color.b = 0;
color_material_->getTechnique(0)->setAmbient(0.5 * color);
color_material_->getTechnique(0)->setDiffuse(color);

And these are the results:

First RTT:

Image

Any other RTT:

Image

So it seems that in the other RTTs, it somehow ignores this custom material and it always uses the mesh-embedded one (plus the incorrect lighting). If you inspect the last image closely, you'll see the lower left part of the image has a blue-y shade, which corresponds to the blue color in the proper image with embedded materials (the very first one).

Do you have any idea what other kind of setup is needed per thread to get all RTTs rendering correctly?

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5534
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1394

Re: Two SceneManagers from two threads - rendering problems

Post by dark_sylinc »

1.9 uses OpenGL on Linux, and OpenGL has been historically unfriendly to multithreading.

Internally the OpenGL context belongs to a single thread, and that's most likely where your problems come from. It may work to force Ogre to unbind the GL context and drawable from the thread when thread A is done using it, and then force Ogre to bind the GL context and drawable into thread B.

Look for glXMakeCurrent/eglMakeCurrent/glXMakeContextCurrent calls in Ogre's source code.

However OpenGL and multiple threads is a world of massive pain and lots of headaches. It may not even work reliably.

Plus, if Ogre makes even a single OpenGL call in a thread where the GL context is not "current", things are going to break.

peci1
Gnoblar
Posts: 2
Joined: Sun Aug 10, 2025 2:59 pm

Re: Two SceneManagers from two threads - rendering problems

Post by peci1 »

Oh, wow, you're right!

I've changed render method to

Code: Select all

void render()
{
  static std::mutex renderingMutex;
  std::lock_guard<std::mutex> lock(renderingMutex);
  const auto gl_render_system = static_cast<Ogre::GLRenderSystem*>(root->getRenderSystem());
  gl_render_system->_getMainContext()->setCurrent();

  rt_->update();
  
cv::Mat rectImg(rectRows, rectCols, this->cvImageType); const Ogre::PixelBox pb(rt_->getWidth(), rt_->getHeight(), 1, this->config.pixelFormat, rectImg.data); rt_->copyContentsToMemory(pb);
gl_render_system->_getMainContext()->endCurrent(); }

And similarly the OGRE init and nodelet setup functions. I'll probably make a LockGuard that locks the mutex and calls setCurrent()/endCurrent() automatically.

And it works pretty nicely now!

What took me a while and a few GLX segfaults was to figure out I also have to call endCurrent() right after the OGRE init in the very first thread. That's probably not something that's usually done, but here it is correct, if all accesses to GLX calls are protected by the setCurrent()/endCurrent() calls.

Thanks a lot!