Problem with "critical" state caching?

Discussion area about developing or extending OGRE, adding plugins for it or building applications on it. No newbie questions please, use the Help forum for that.
flavio
Gnoblar
Posts: 13
Joined: Tue Sep 01, 2015 4:41 pm

Problem with "critical" state caching?

Post by flavio »

Dear Ogre developers,

On Windows, I recently had to upgrade Sight (https://github.com/IRCAD/sight) from Ogre 1.12.10 to Ogre 14.0.1. It was a bit painful, but in the end, I managed to solve all the issues. However, I am not really happy with the last fix I had to perform, because I needed to patch Ogre sources.

The problem was the following. In SightViewer, a medical imaging viewer, we display four split views. The first view is well shown, while the three others are completely black. Looking at the command buffer in NSight, I could understand that it was completely screwed up. After hours of debugging, I found that the problem has occurred since the introduction of this new define OGRE_ENABLE_STATE_CACHE_CRITICAL OgreGL3PlusStateCacheManager.cpp @8891ed. Commenting out this define solves the issue.

I noticed there was a comment mentioning that this change was supposed to be safe for multi-context. Apparently, this is not in my case. Each view is based on a QOpenGLWidget. I know it's not the recommended way, but that's the only way I found to overlay Qt widgets on top of the Ogre 3D scenes. I perform the compositing of Qt and Ogre myself in the end. I know people have struggled for years on that topic and I am somehow proud to have solved that, and in the other side, I am not that confident about that code and I fear it could be a bit fragile, because of Qt and Ogre dealing with the same OpenGL context... :) Maybe it does not matter for this particular problem, but it might, so I prefer to mention it.

Well, in the end, can I kindly ask you if you know for sure if this state caching is really safe in multi-context scenarios? If yes, would you mind adding a CMake variable to disable it anyway, like the "standard" state cache? We could patched it in the vcpkg build, but I am afraid of not being able to patch it in the Debian package (which is really not up-to-date currently, I am trying to help there, but that's another story...)

Thank you in advance for your help, and above all, many thanks for this amazing 3D engine. :)

paroj
OGRE Team Member
OGRE Team Member
Posts: 2108
Joined: Sun Mar 30, 2014 2:51 pm
x 1134

Re: Problem with "critical" state caching?

Post by paroj »

flavio wrote: Mon Oct 23, 2023 10:15 am

On Windows, I recently had to upgrade Sight (https://github.com/IRCAD/sight) from Ogre 1.12.10 to Ogre 14.0.1. It was a bit painful,

can you elaborate on what was particularly painful? The upgrade 1.12 to 14 should have been rather smooth..

flavio wrote: Mon Oct 23, 2023 10:15 am

I found that the problem has occurred since the introduction of this new define OGRE_ENABLE_STATE_CACHE_CRITICAL OgreGL3PlusStateCacheManager.cpp @8891ed. Commenting out this define solves the issue.

this disables state caching completely then. There have been issues with multi-context use since the introduction of the variable, so currently it only guards the caching of VAOs.
VAOs are definitely per-context and not caching that state hurts performance. I have also verified that using multiple render windows, while having VAO caching works.

On the other hand, not caching that means that VAO is always re-bound on rendering. This might be what you are actually after if another VAO is bound by Qt behind your back, re-binding fixes rendering.
Can you check this? Also take a look at: https://stackoverflow.com/questions/132 ... e-contexts

flavio
Gnoblar
Posts: 13
Joined: Tue Sep 01, 2015 4:41 pm

Re: Problem with "critical" state caching?

Post by flavio »

paroj wrote: Mon Oct 23, 2023 10:58 am
flavio wrote: Mon Oct 23, 2023 10:15 am

On Windows, I recently had to upgrade Sight (https://github.com/IRCAD/sight) from Ogre 1.12.10 to Ogre 14.0.1. It was a bit painful,

can you elaborate on what was particularly painful? The upgrade 1.12 to 14 should have been rather smooth..

Here is an almost exhaustive list:

  • There was somehow a change in Ogre::GpuProgramParameters::setNamedConstant() signature iirc, and initially we fixed it by casting texunits numbers in unsigned int... Bad idea, because the unsigned signature does not work for samplers, only the signed int signature works...
  • render-to-vertex buffer output varying naming (oPos -> xfb_position for instance) (easily spot)
  • unused program parameters trigger errors (or maybe only warnings?) (had to fix them, but not that easy in our code base with programs generated by python scripts...)
  • I have somehow "less" registers available in shader programs, I did not understand why. I had to reduce the number of reserved slots for lights from 10 to 8 (I can live with it for now)...
  • The last was also a bit challenging to find (recompiled Ogre many times to find when the regression occurred), the separable shader object new default value also breaks the multiple widget rendering... The first works but the others turn into pure white color... I just set the option to False to fix it, but I did not really understand the reason... :(

You can find the full diff here https://git.ircad.fr/sight/sight/-/merg ... /980/diffs, but beware there are unrelated changes due to the switch to MSVC2022.

paroj wrote: Mon Oct 23, 2023 10:58 am
flavio wrote: Mon Oct 23, 2023 10:15 am

I found that the problem has occurred since the introduction of this new define OGRE_ENABLE_STATE_CACHE_CRITICAL OgreGL3PlusStateCacheManager.cpp @8891ed. Commenting out this define solves the issue.

this disables state caching completely then. There have been issues with multi-context use since the introduction of the variable, so currently it only guards the caching of VAOs.
VAOs are definitely per-context and not caching that state hurts performance. I have also verified that using multiple render windows, while having VAO caching works.

On the other hand, not caching that means that VAO is always re-bound on rendering. This might be what you are actually after if another VAO is bound by Qt behind your back, re-binding fixes rendering.
Can you check this? Also take a look at: https://stackoverflow.com/questions/132 ... e-contexts

Yeah actually, I understood it wrong, I only have a single context, which is created by Qt. So I should not be affected by any context change issue. The real thing I was worried about is exactly what you said, that Qt might bind another VAO behind my back (and probably other states...). And of course, there is no way you can't anticipate this in the state cache. Another workaround would be to manually bind another VAO in Ogre to force the binding of the first VAO at the beginning of the Ogre rendering scope. I have a very limited number of draw calls, so I can really live with it. But I have no idea right now how to achieve this safely, maybe rendering a simple object out of screen?

paroj
OGRE Team Member
OGRE Team Member
Posts: 2108
Joined: Sun Mar 30, 2014 2:51 pm
x 1134

Re: Problem with "critical" state caching?

Post by paroj »

flavio wrote: Mon Oct 23, 2023 2:19 pm
  • There was somehow a change in Ogre::GpuProgramParameters::setNamedConstant() signature iirc, and initially we fixed it by casting texunits numbers in unsigned int... Bad idea, because the unsigned signature does not work for samplers, only the signed int signature works...

this follows what OpenGL does, see: https://registry.khronos.org/OpenGL-Ref ... form.xhtml

you can also provide a PR adding unsigned support for Ogre, if you think it makes sense.

flavio wrote: Mon Oct 23, 2023 2:19 pm
  • unused program parameters trigger errors (or maybe only warnings?) (had to fix them, but not that easy in our code base with programs generated by python scripts...)

you can tell ogre to ignore unused parameters in gpu programs instead. But fixing that is the better way. Also, the unused paramter elimination is done by your GPU driver and not by Ogre. So I guess this is rather caused by a driver update. We merely report the results.

flavio wrote: Mon Oct 23, 2023 2:19 pm
  • I have somehow "less" registers available in shader programs, I did not understand why. I had to reduce the number of reserved slots for lights from 10 to 8 (I can live with it for now)...

might be related to OGRE_MAX_SIMULTANEOUS_LIGHTS = 8. This means you would have never got more than 8 lights anyway. Probably this is related to the unused program errors.

flavio wrote: Mon Oct 23, 2023 2:19 pm
  • The last was also a bit challenging to find (recompiled Ogre many times to find when the regression occurred), the separable shader object new default value also breaks the multiple widget rendering... The first works but the others turn into pure white color... I just set the option to False to fix it, but I did not really understand the reason... :(

Separable programs have slightly different matching rules. Probably some of these:
https://www.khronos.org/opengl/wiki/Sha ... m_matching

flavio wrote: Mon Oct 23, 2023 2:19 pm

The real thing I was worried about is exactly what you said, that Qt might bind another VAO behind my back (and probably other states...). And of course, there is no way you can't anticipate this in the state cache.

you can get the active VAO via:

Code: Select all

glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &current_vao);

before starting rendering with Qt and then bind that again before giving rendering back to Ogre. This way the cache is still valid.

Alternatively, you can create a dummy renderable with 0 vertices that you render before everything else, with the solve purpose of invalidating the VAO in the cache.

flavio
Gnoblar
Posts: 13
Joined: Tue Sep 01, 2015 4:41 pm

Re: Problem with "critical" state caching?

Post by flavio »

Thanks a lot Pavel for your clear advice...

I tried the trick with the VAO, but it did not work, for a very good reason. I made a wrong assumption and actually, my whole pipeline setup may have to be reconsidered.

Let me explain what I tried to achieve, what I did, and why it does not work.

What I tried to achieve:
I wanted to be able to overlay Qt widgets over many 3D Ogre widgets.

What I did
The "recommended" QWindow way does not allow, in my experience, to achieve this, with transparency. Thus, I derived from QOpenGLWidget, so that I get a Qt OpenGL context and the render target associated with the painting area. I struggled with Ogre::RenderWindow because I could not prevent it from writing in the back buffer in the end. Instead I created render textures for each of the layer (viewport) composing what was a render window. Since a RenderTexture, in the end (with compositors, etc...) always render in its own FBO, I hacked a post blit pass with a quad and a manual render, in order to render in the Qt FBO . This works successfully in most cases.

Why it does not work
I had a wrong understanding of Qt::AA_ShareOpenGLContexts. I understood I had only one GL context, but no, I got one GL context for each widget. All context are shared, so yes most things that can be shared work. But VAO can't be shared that's why it does not work. Since I hacked a bit the whole stuff by not using Ogre::RenderWindow, Ogre is never aware of the OpenGL context change. Thus, the code supposed to recreate the VAO if the context changes is not triggered. I had other symptoms about that, for instance when I reuse a mesh resource loaded by the Ogre::MeshManager:

Code: Select all

Error: glBindVertexArray failed with GL_INVALID_OPERATION in bindGLVertexArray at /home/fbridault/dev/3rdParty/OGRECave/RenderSystems/GL3Plus/src/OgreGL3PlusStateCacheManager.cpp(274)

What to do now?
I spent the whole day to understand that, and I did not find any ideal solution yet.

  • I tried to "inject" the current OpenGL context to the render system, but obviously this can't be done directly because of the abstraction layer.

  • I tried to use dummy render windows and create the render targets from them (addViewport()), then using these targets manually without the window, but unfortunately I can't retrieve the textures (no 1-1 match) to do the "post" blit pass into the Qt fbo. I don't think compositors can help because they would imply binding an Ogre FBO...

  • I'm now thinking about ugly solutions such as casting the rendersystem and patching a GL3Plus header somewhere to inject the context, but that sounds really awful... For now I could also live without "shared" meshes, but the static Rectangle2D of Ogre::CompositorInstance would still fail, and that I can't live with it.

Well I don't know if that was clear enough. I understand I'm really doing offroad with Ogre, but this overlay with Qt is really so useful and convenient... I can't really go backward now.

If you have again any advice, I would be glad to hear it. :)

Thanks again !

paroj
OGRE Team Member
OGRE Team Member
Posts: 2108
Joined: Sun Mar 30, 2014 2:51 pm
x 1134

Re: Problem with "critical" state caching?

Post by paroj »

flavio wrote: Mon Nov 27, 2023 9:12 pm

The "recommended" QWindow way does not allow, in my experience, to achieve this, with transparency.

yes, the QWindow integration basically instructs Ogre to draw on top of whatever widget you give it. What you try to do (as far as I got it) requires instructing either Ogre or Qt to draw into an offscreen buffer and then let the other one do the compositing.

There are some examples on github, where people let QML do the compositing e.g. https://github.com/raynaudoe/qml-ogre

flavio wrote: Mon Nov 27, 2023 9:12 pm

I'm now thinking about ugly solutions such as casting the rendersystem and patching a GL3Plus header somewhere to inject the context, but that sounds really awful...

it might be the best solution though. As you noticed Ogre was never really meant to cooperate with another OpenGL renderer on the same frame. So maybe you must add a signal to instruct it that the context was messed with. Similar to what we do with _notifySurfaceDestroyed on Android.
You could make it work and then create PR to discuss how to properly integrate it with Ogre.

flavio wrote: Mon Nov 27, 2023 9:12 pm

I tried to "inject" the current OpenGL context to the render system, but obviously this can't be done directly because of the abstraction layer.

oh, you actually can use an external GL context, see the "currentGLContext" parameter here:
https://ogrecave.github.io/ogre/api/lat ... 6f6b672620

If you just want to render "complex" widgets on top of Ogre, I might draw your attention to our imgui integration. I works flawlessly as all drawing is still done by Ogre and - as a bonus - you dont have to use Qt.

flavio
Gnoblar
Posts: 13
Joined: Tue Sep 01, 2015 4:41 pm

Re: Problem with "critical" state caching?

Post by flavio »

Hi, yeah you got it right. :wink:

Actually, I think I found a workaround, with the help of a trick I used before with the QWindow approach. And yeah you assumed well, this involves this "currentGLContext" parameter.

On top of my "hand-managed" render targets, I create beforehand an Ogre::RenderWindow for each widget... but I never render it. It helps to "capture" the current GL context and then, whenever I need it, I simply call render_system->_setRenderTarget(m_context_switch_window);

It still very hacky but this way I no longer need any patch. Even in debug mode I did not see so far any OpenGL error (except the transform feedback glAttachShader(), but it is created by the debug code itself (OgreGLSLProgram.cpp:106), so I ignore it for now).

I hope I will not find any hidden issue, I'll do extensive tests tomorrow.

Thanks again for your help.