Performance Improvement of IMMaterialPool.GetMaterial()

Avatar
  • updated

Hi.

I have always used it.

I am running a large number of Draw.Quad() in Immediate Mode in Shapes and am having trouble with the frame rate drop that occurs around 1300 executions.

I have investigated in Unity's Deep Profile and found that it is mainly the execution of the following methods that are taking a long time.

  • IMMaterialPool.GetMaterial()
  • IMDrawer.GetMaterialKeywords()
  • MetaMpb.PreAppendCheck()

Image 752

Of these, we noted that TryGetValue() is the reason why IMMaterialPool.GetMaterial() is taking so long to process.

I made a small improvement myself.

Before:

IMDrawer.cs

            if( DrawCommand.IsAddingDrawCommandsToBuffer ) {
                Draw.style.renderState.shader = sourceMat.shader;
                Draw.style.renderState.keywords = GetMaterialKeywords( sourceMat );
                Draw.style.renderState.isTextMaterial = drawType == DrawType.TextPooledPersistent || drawType == DrawType.TextAssetClone;

RenderState.cs

        public Shader shader;
        public string[] keywords; // this is gross
        public bool Equals( RenderState other ) =>
            Equals( shader, other.shader ) &&
            StrArrEquals( keywords, other.keywords ) &&
            zTest == other.zTest &&

After:

IMDrawer.cs

            if( DrawCommand.IsAddingDrawCommandsToBuffer ) {
                Draw.style.renderState.shader = sourceMat.shader;
                Draw.style.renderState.isTextMaterial = drawType == DrawType.TextPooledPersistent || drawType == DrawType.TextAssetClone;

                string[] keywords = GetMaterialKeywords(sourceMat);
                Draw.style.renderState.keywords = keywords;
                Draw.style.renderState.comparisonKeywords = string.Join(string.Empty, keywords);

*I believe that "string.Empty" should be a string that is never used as a keyword.

RenderState.cs

        public Shader shader;
        public string[] keywords; // this is gross
        public string comparisonKeywords;
        public bool Equals( RenderState other ) =>
            Equals( shader, other.shader ) &&
            comparisonKeywords == other.comparisonKeywords &&
            zTest == other.zTest &&

GetHashCode() in RenderState.cs has also been improved.

        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = ( shader != null ? shader.GetHashCode() : 0 );
                if (comparisonKeywords != null)
                    hashCode = ( hashCode * 397 ) ^ comparisonKeywords.GetHashCode();

                hashCode = ( hashCode * 397 ) ^ (int)zTest;
                hashCode = ( hashCode * 397 ) ^ zOffsetFactor.GetHashCode();
                hashCode = ( hashCode * 397 ) ^ zOffsetUnits;
                hashCode = ( hashCode * 397 ) ^ (int)colorMask;
                hashCode = ( hashCode * 397 ) ^ (int)stencilComp;
                hashCode = ( hashCode * 397 ) ^ (int)stencilOpPass;
                hashCode = ( hashCode * 397 ) ^ (stencilRefID << 16) | (stencilReadMask << 8) | stencilWriteMask;

                return hashCode;
            }
        }

The results of the before and after profiles are also attached.
*4727 is the number of calls.

Before:

Image 753

After:

Image 754




It may be that cases like mine that are executed in large numbers are rare, or it may be that there is nothing we can do about it due to the processing, but is there any other room for improvement?

EDIT: The long execution time is due to the use of Deep Profile. If it is not used, the time can be as low as 6-8 ms, but the problem becomes more apparent when executed on mobile devices.

Reporting a bug? please specify Unity version:
2022.3.1f1
Reporting a bug? please specify Shapes version:
4.2.1
Reporting a bug? please specify Render Pipeline:
Built-in render pipeline
Avatar
ushui

I know I should not have originally altered the code provided, but I have done further research since the above.

As a result, we have further improved the performance.

I have sent you an email with the improved code, though it may be inconvenient for you.

(Since the content is the Shapes source code itself, we decided it was not appropriate to post it in this forum.)

After applying the code, all drawing methods, including Draw.Quad(), are about twice as fast.

Before:

Image 755

After:

Image 756


There are three main improvements.

  • Dictionary keys were changed to int.
  • The role of the RenderState structure was reduced and left to the IMMaterialPool, so that processing that does not need to be done is not performed.
  • PreAppendCheck() by adding "in" to the two arguments, which reduced the calls to String.memcpy() in IMDrawer by about 35%.

However, this is still not the performance I am looking for.
Apart from the above, I was able to improve it a bit by not running ColorSpaceAdjusted() in Quad_Internal().
(This is because my project's color space is Gamma)

Upon closer inspection, it seems that Draw.Quad() alone was running about 10000 times in my project.

It is no wonder this is slowing me down, so I am considering reducing the number of calls to Draw.Quad() in the first place.

Shapes is a great asset and I hope to continue using it.

If there is anything else we can do, please let us know.