Page 1 of 1

[2.1] How to set "z-index" for UI elements ?

Posted: Mon Nov 05, 2018 11:48 pm
by mrmclovin
Ogre Version: 2.1 :?:
Operating System: MacOs :?:
Render System:Metal :?:

I render my GUI as textures on a custom MovableObject plane (with the projection matrix set to identity). Each GUI movable object is attached to a SceneNode which is a child directly under the scene manager's root node. (I'm using V2 rendering pipeline btw)

I have now come into the problem when elements are overlapping. I want to be able to control their "z-index", i.e. order of rendering precedence.

What is the best way to do this?

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Tue Nov 06, 2018 4:20 am
by dark_sylinc
UI is a special case, particularly if you're rendering yourself.

Ogre currently doesn't offer you much flexibility out of the box for this case. You'll probably want to disable sorting for that RenderQueue via RenderQueue::setSortRenderQueue; in which case they will be rendered in the order they're added to the queue i.e. the order in which the MovableObjects were created (and the order in which Renderables appear inside MovableObject::mRenderables). Or allow sorting and play with Renderable::setRenderQueueSubGroup (but the limit is very low, you only get like 4 subgroups).
By reserving the last 10 RenderQueue groups for UI + using setRenderQueueSubGroup, you should be able to get 10*4 = 40 layers of Z depth with simple math trick:

Code: Select all

movableObject->setRenderQueueGroup( z_order / 4u + 244u );
movableObject->mRenderables[i]->setSubRenderQueueGroup( z_order % 4u ) .
For UI you may want to implement a custom compositor pass instead, that mimics what RenderQueue::render does.

If you need a reference implementation on how to do that, take a look at Colibri. The key places you need to look at are CompositorPassColibriGuiProvider.cpp. This one is tells the Compositor to understand the following in compositor scripts:

Code: Select all

pass custom colibri_gui
{
}
CompositorPassColibriGui.cpp which implements the actual pass, and ColibriManager::render which is called by the pass and performs tasks very similar to RenderQueue::render (like preparing the Hlms), plus stuff specific to Colibri's.

In theory instead of writing your own RenderQueue::render replacement, you could just call RenderQueue::addRenderable in the order you want and then call mRenderQueue::render

By implementing your own compositor pass, you will be in full control of what's rendered and in which order.

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Tue Nov 06, 2018 10:42 am
by mrmclovin
dark_sylinc wrote:
Tue Nov 06, 2018 4:20 am
UI is a special case, particularly if you're rendering yourself.

Ogre currently doesn't offer you much flexibility out of the box for this case. You'll probably want to disable sorting for that RenderQueue via RenderQueue::setSortRenderQueue; in which case they will be rendered in the order they're added to the queue i.e. the order in which the MovableObjects were created (and the order in which Renderables appear inside MovableObject::mRenderables). Or allow sorting and play with Renderable::setRenderQueueSubGroup (but the limit is very low, you only get like 4 subgroups).
By reserving the last 10 RenderQueue groups for UI + using setRenderQueueSubGroup, you should be able to get 10*4 = 40 layers of Z depth with simple math trick:

Code: Select all

movableObject->setRenderQueueGroup( z_order / 4u + 244u );
movableObject->mRenderables[i]->setSubRenderQueueGroup( z_order % 4u ) .
For UI you may want to implement a custom compositor pass instead, that mimics what RenderQueue::render does.

If you need a reference implementation on how to do that, take a look at Colibri. The key places you need to look at are CompositorPassColibriGuiProvider.cpp. This one is tells the Compositor to understand the following in compositor scripts:

Code: Select all

pass custom colibri_gui
{
}
CompositorPassColibriGui.cpp which implements the actual pass, and ColibriManager::render which is called by the pass and performs tasks very similar to RenderQueue::render (like preparing the Hlms), plus stuff specific to Colibri's.

In theory instead of writing your own RenderQueue::render replacement, you could just call RenderQueue::addRenderable in the order you want and then call mRenderQueue::render

By implementing your own compositor pass, you will be in full control of what's rendered and in which order.
Thank you for the examples. I had a look on the Compositor solution and it seems like a rabbit hole for me since I have zero knowledge of Compositors :D . However, I want to give it a try though.

Looking at the examples you suggested, there seems to be many mechanisms at work, and many things I can do wrong. I want to figure out how much I can strip the code down to a bare minimum.

Could you just give me the general idea of how the process of the end result could work?

E.g., given that I have a bunch of movable objects, do X to send them to the compositor, and use Y to send the z_index value from C++. In the compositor, use the X feature to do the order, and then Y to render the final order.

I'll read up on compositor scripts and continue to examine your examples, but I think having the general idea in the back of my head would really help!

Thank you for your help!

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Sun Nov 11, 2018 12:13 am
by mrmclovin
dark_sylinc wrote:
Tue Nov 06, 2018 4:20 am
For UI you may want to implement a custom compositor pass instead, that mimics what RenderQueue::render does.
Is RenderQueue::render part of v1 and does not involve v2 stuff? I'm trying to sort out the relation (or the purpose) between RenderQueue::render and CompositorPass::execute.

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Tue Nov 13, 2018 5:10 pm
by dark_sylinc
Apologies for the late reply. I've been very busy.

These files do not need stripping at all (or minimal) since they're doing exactly what you need:
  • CompositorPassColibriGuiProvider.cpp & .h. Just change "colibri_gui" and "ColibriGui" words for your own (whatever you want). The m_colibriManager pointer can just be removed. It's there basically to send it to all of our instances we create.
    This snippet in particular:

    Code: Select all

    if( customId == "colibri_gui" )
        return OGRE_NEW CompositorPassColibriGuiDef( parentTargetDef );
    Is what makes the scripts work:

    Code: Select all

    pass custom colibri_gui
    {
    }
  • CompositorPassColibriGuiDef.h For all intent and purposes, you could declare an empty class:

    Code: Select all

    class CompositorPassColibriGuiDef : public CompositorPassDef
    {
    public:
    public:
        CompositorPassColibriGuiDef( CompositorTargetDef *parentTargetDef ) :
            CompositorPassDef( PASS_CUSTOM, parentTargetDef )
        {
            mProfilingId = "User readable text for Profiling";
        }
    };
  • CompositorPassColibriGui.cpp & .h actually contain minimal code. Bare bone stripped they should look like this:

    Code: Select all

    CompositorPassColibriGui::CompositorPassColibriGui( const CompositorPassColibriGuiDef *definition,
                                                        SceneManager *sceneManager,
                                                        const RenderTargetViewDef *rtv,
                                                        CompositorNode *parentNode ) :
        CompositorPass( definition, parentNode ),
        mSceneManager( sceneManager ),
        mDefinition( definition )
    {
        initialize( rtv );
    
        setResolutionToColibri( mAnyTargetTexture->getWidth(), mAnyTargetTexture->getHeight() );
    }
    //-----------------------------------------------------------------------------------
    void CompositorPassColibriGui::execute( const Camera *lodCamera )
    {
        //Execute a limited number of times?
        if( mNumPassesLeft != std::numeric_limits<uint32>::max() )
        {
            if( !mNumPassesLeft )
                return;
            --mNumPassesLeft;
        }
    
        profilingBegin();
    
        CompositorWorkspaceListener *listener = mParentNode->getWorkspace()->getListener();
        if( listener )
            listener->passEarlyPreExecute( this );
    
        executeResourceTransitions();
    
        //Fire the listener in case it wants to change anything
        if( listener )
            listener->passPreExecute( this );
    
        /// DO WHATEVER YOU WANT HERE
    
        if( listener )
            listener->passPosExecute( this );
    
        profilingEnd();
    }
And that's it! Before calling GraphicsSystem::setupResources(), you should register the compo provider so that when the .compositor scripts are parsed, Ogre will understand how to read them and call your provider:

Code: Select all

Ogre::CompositorPassColibriGuiProvider *compoProvider = OGRE_NEW Ogre::CompositorPassColibriGuiProvider();
Ogre::CompositorManager2 *compositorManager = mRoot->getCompositorManager2();
compositorManager->setCompositorPassProvider( compoProvider );

GraphicsSystem::setupResources()
Now... that's it?
OK I lied. I left out ColibriManager::render, which gets called inside CompositorPassColibriGui::execute; and perform the actual drawing commands by hand.
If we want to reuse the RenderQueue to our favour, you could do this where I left "/// DO WHATEVER YOU WANT HERE" from CompositorPassColibriGui::execute:

Code: Select all

mRenderQueue->clear();
mRenderQueue->renderPassPrepare( false, false );

for_every_movableObject_widget_you_have() //Add them in the order you want them to be rendered!!!
{
    RenderableArray::const_iterator itRend = movableObj->mRenderables.begin();
    RenderableArray::const_iterator enRend = movableObj->mRenderables.end();
    
    while( itRend != enRend )
    {
        mRenderQueue->addRenderableV2( threadIdx = 0, renderQueue = 80, false, renderable, movableObject );
        ++itRend;
    }
}

uint8 firstRq = 80;
uint8 lastRq = 80;
mRenderQueue->render( renderSystem, firstRq, lastRq, 0, false, false );
That should be all. Make sure that the RenderQueue setSortRenderQueue for the RenderQueue IDs you use is set to DisableSort; and that the mode is set to V2_FAST.
In this example I'm using RenderQueue ID 80 for no particular reason.

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Tue Nov 13, 2018 10:30 pm
by mrmclovin
Thank you for taking the time. I know you have a lot to do.
dark_sylinc wrote:
Tue Nov 13, 2018 5:10 pm
If we want to reuse the RenderQueue to our favour, you could do this where I left "/// DO WHATEVER YOU WANT HERE" from CompositorPassColibriGui::execute:
Do I understand it correctly, that it's the call to mRenderQueue->addRenderableV2(...) that will determine the order of which the movables will be drawn? I.e. this is the place where I can do my own sorting and control which movable overlap the other ?

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Tue Nov 13, 2018 11:20 pm
by dark_sylinc
Yes, the order in which Renderables are added via addRenderableV2 determines render order (first added get rendererd first) as long as sorting mode is set to DisableSort, otherwise the RenderQueue will internally reorder them.

That applies to objs living in the same render queue.
If you call:
RenderQueue->addRenderableV2( 0, renderQueue = 81, false, renderableA, movableObject );
RenderQueue->addRenderableV2( 0, renderQueue = 80, false, renderableB, movableObject );

Then renderableB will be rendered before renderableA, because you added renderableB to renderQueue 80, and renderableA to rq 81; and all objects in rq 80 get rendered before the ones in rq 81.

Re: [2.1] How to set "z-index" for UI elements ?

Posted: Wed Nov 14, 2018 11:26 am
by mrmclovin
dark_sylinc wrote:
Tue Nov 13, 2018 11:20 pm
Yes, the order in which Renderables are added via addRenderableV2 determines render order (first added get rendererd first)
Thank you for your help. I should have everything I need to continue from here!