Page 1 of 1

[2.3] Getting clarity on things that might break instancing

Posted: Mon Nov 21, 2022 8:19 am
by psysu

Hi, im upgrading our current project which is running on Ogre 1.x to Ogre-next. i have implemented a new Hlms which inherits features from HlmsPbs, to add additional properties on top of HlmsPbs for our use cases.

while im working on this, im not clear about how the auto-instancing works in Ogre-Next. if someone can provide clarity on how auto-instancing in Ogre-Next works and stuffs that can break the instancing, it would be really helpful.

thanks


Re: [2.3] Getting clarity on things that might break instancing

Posted: Tue Nov 22, 2022 6:26 pm
by dark_sylinc

From a 10.000km overall view, auto instancing reduces to this:

Code: Select all

// We have this:
for( int i < numObjects )
{
	drawCall( renderable[i] );
}

// We want to turn it into this:
drawCalls( renderable, numObjects );

The way we achieve this is conceptually very simple:

Code: Select all

lastDrawnObjStart = 0;
numObjsToDraw = 0;
for( int i < numObjects )
{
    if( needsMoreCommands[i] )
    {
        // Draw everything accumulated so far
        drawCalls( &renderable[lastDrawnObjStart], numObjsToDraw );
        lastDrawnObjStart = numObjsToDraw;
        numObjsToDraw = 0;

    // Set new shader
    setShader( renderable[i] );

    // Keep accumulating
    ++numObjsToDraw;
}
else
{
    // Keep accumulating
    ++numObjsToDraw;
}
}

// Draw everything accumulated so far
drawCalls( &renderable[lastDrawnObjStart], numObjsToDraw );

That is, we start accumulating objects to render until we find a something that breaks the instancing, submit what we have so far, issue that breaking call, and then start accumulating again.

Now, what you are you asking is "what causes needsMoreCommands[i] to be true".

Well... anything that isn't a draw call:

  • Any change in HlmsBlendblock

  • Any change in HlmsMacroblock

  • Any change in HlmsSamplerblock

  • Needing a different shader

  • Any change in vertex format

  • Binding new const / texture buffers

  • Any change in texture pools

Because of a technique called AZDO and MultiDraw, we don't usually need to break auto instancing for different meshes or vertex buffers.

As for textures, we try to aggressively use AutomaticBatching, where we put lots of textures together into one pool (i.e. we externally pretend a single TextureGpu is alone a Type2D, when internally it actually is a slice of a Type2DArray) so that we don't have to issue setTexture() calls frequently, thus increasing the chances of auto instancing.
If 2 Items use different textures but they are living in the same pool, auto instancing won't be broken because internally the same texture is still bound.

In short, every time we need to call:

Code: Select all

commandBuffer->addCommand<T>()

the instancing will be broken up.

If you are writing your own HlmsPbs modification, you need to be care of not calling commandBuffer->addCommand<T>() with every fillBuffersFor call.

We use key sorting in OgreRenderQueue.cpp (as long as the sort mode for that particular queue isn't DisableSort) to group everything by state as much as possible (e.g. transparents are the hardest because sorting back to front takes priority) and thus maximize the chance of auto instancing (e.g. all Items using the same shader should be grouped together).


Re: [2.3] Getting clarity on things that might break instancing

Posted: Thu Nov 24, 2022 12:36 pm
by psysu

Thanks for answering this in detail.

Also, i have another question, why stuffs like Ogre::RenderQueue::RenderableListener not present in Ogre-Next, im guessing here.. is it because of this new approach. is there a way to get callbacks for Ogre::Renderable that is about to be rendered in Ogre-Next ?


Re: [2.3] Getting clarity on things that might break instancing

Posted: Thu Nov 24, 2022 3:49 pm
by dark_sylinc

Per-renderable listeners would sit in the most performance sensitive area (e.g. if we have 100k objects too render, that's 100k listener calls), so we discourage this type of approach on purpose.

If possible try to refactor things at per pass level (e.g. CompositorWorkspaceListener::passPreExecute or passEarlyPreExecute; or HlmsPbs::preparePassHash or HlmsListener::preparePassHash).

For example you can place all your specific Renderables into a specific RenderQueue and create a render_scene pass just for that RenderQueue ID. Therefore you can be sure pass-level calls like passPreExecute will only deal with Renderables that you care about.

In a few cases it's possible to refactor things per renderable at material-assignation time (i.e. HlmsListener::shaderCacheEntryCreated or propertiesMergedPreGenerationStep).

But if you really need per-renderable calls, every frame; then the only place to do this reliably is to overload Hlms::fillBuffersFor.


Re: [2.3] Getting clarity on things that might break instancing

Posted: Fri Nov 25, 2022 5:29 am
by psysu

Thanks, i will look into these things you mentioned