A small optimization

Jun 3, 2011 at 12:26 PM
Edited Jun 6, 2011 at 4:41 PM

Hi there, while profiling my game, I found that Krypton generated a significant amount of garbage during the draw method. After investigating, I found that at each draw call, the render helper called the List.ToArray() Method. So here is my small contribution, the modified KryptonRenderHelper which is using arrays instead of lists. Plus, each frame the DrawSquaredQuad method was creating an array of VertexPositionColorTexture. So instead, I moved the array and cached all the alues, updating them instead of creating new ones each frame:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Krypton.Lights;

namespace Krypton
{
    public enum BlendTechnique
    {
        Add = 1,
        Multiply = 2,
    };

    public enum BlurTechnique
    {
        Horizontal = 1,
        Vertical = 2,
    };

    public class KryptonRenderHelper
    {
        #region Static Unit Quad

        private static VertexPositionTexture[] UnitQuad = new VertexPositionTexture[]
        {
            new VertexPositionTexture()
            {
                Position = new Vector3(-1, 1, 0),
                TextureCoordinate = new Vector2(0, 0),
            },
            new VertexPositionTexture()
            {
                Position = new Vector3(1, 1, 0),
                TextureCoordinate = new Vector2(1, 0),
            },
            new VertexPositionTexture()
            {
                Position = new Vector3(-1, -1, 0),
                TextureCoordinate = new Vector2(0, 1),
            },
            new VertexPositionTexture()
            {
                Position = new Vector3(1, -1, 0),
                TextureCoordinate = new Vector2(1, 1),
            },
        };

        #endregion Static Unit Quad

        VertexPositionColorTexture[] quad = new VertexPositionColorTexture[4];
        VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[7];
        Int32[] indicies3;
        Int32[] indicies9;
        Int32[] indicies15;

        // Set this number above the amount of vertex you think will be needed
        private const int MAX_HULLS_VERTEX = 1000;

        private GraphicsDevice mGraphicsDevice;
        private Effect mEffect;
        private ShadowHullVertex[] mShadowHullVertices = new ShadowHullVertex[MAX_HULLS_VERTEX];
        private int mShadowHullVerticesNum = 0;
        private Int32[] mShadowHullIndicies = new Int32[MAX_HULLS_VERTEX];
        private int mShadowHullIndiciesNum = 0;

        public GraphicsDevice GraphicsDevice
        {
            get { return this.mGraphicsDevice; }
        }
        public Effect Effect
        {
            get { return this.mEffect; }
        }

        public ShadowHullVertex[] ShadowHullVertices
        {
            get { return this.mShadowHullVertices; }
        }
        public Int32[] ShadowHullIndicies
        {
            get { return this.mShadowHullIndicies; }
        }

        public KryptonRenderHelper(GraphicsDevice graphicsDevice, Effect effect)
        {
            this.mGraphicsDevice = graphicsDevice;
            this.mEffect = effect;

            for (int i = 0; i < 4; i++)
            {
                this.quad[i] = new VertexPositionColorTexture()
                {
                    Position = Vector3.Zero,
                    Color = Color.White,
                    TextureCoordinate = Vector2.Zero,
                };
            }

            for(int i = 0; i < 7; i++){
                this.vertices[i] = new VertexPositionColorTexture()
                    {
                        Position = Vector3.Zero,
                        Color = Color.White,
                        TextureCoordinate = Vector2.Zero,
                    };
            }

            
            indicies3 = new Int32[]
                {
                    0, 1, 6,
                };

            indicies9 = new Int32[]
                {
                    0, 1, 3,
                    0, 3, 4,
                    0, 4, 6,
                };
            indicies15 = new Int32[]
                {
                    0, 1, 2,
                    0, 2, 3,
                    0, 3, 4,
                    0, 4, 5,
                    0, 5, 6,
                };
        }

        public void BufferAddShadowHull(ShadowHull hull)
        {
            // Why do we need all of these again? (hint: we don't)

            Matrix vertexMatrix = Matrix.Identity;
            Matrix normalMatrix = Matrix.Identity;

            float cos, sin;

            ShadowHullPoint point;
            ShadowHullVertex hullVertex;

            // Create the matrices (3X speed boost versus prior version)
            cos = (float)Math.Cos(hull.Angle);
            sin = (float)Math.Sin(hull.Angle);

            // vertexMatrix = scale * rotation * translation;
            vertexMatrix.M11 = hull.Scale.X * cos;
            vertexMatrix.M12 = hull.Scale.X * sin;
            vertexMatrix.M21 = hull.Scale.Y * -sin;
            vertexMatrix.M22 = hull.Scale.Y * cos;
            vertexMatrix.M41 = hull.Position.X;
            vertexMatrix.M42 = hull.Position.Y;

            // normalMatrix = scaleInv * rotation;
            normalMatrix.M11 = (1f / hull.Scale.X) * cos;
            normalMatrix.M12 = (1f / hull.Scale.X) * sin;
            normalMatrix.M21 = (1f / hull.Scale.Y) * -sin;
            normalMatrix.M22 = (1f / hull.Scale.Y) * cos;

            // Where are we in the buffer?
            var vertexCount = mShadowHullVerticesNum;

            // Add the vertices to the buffer
            for (int i = 0; i < hull.NumPoints; i++)
            {

                // Transform the vertices to screen coordinates
                point = hull.Points[i];
                Vector2.Transform(ref point.Position, ref vertexMatrix, out hullVertex.Position);
                Vector2.TransformNormal(ref point.Normal, ref normalMatrix, out hullVertex.Normal);

                hullVertex.Color = Color.Black;

                this.mShadowHullVertices[mShadowHullVerticesNum] = hullVertex; // could this be sped up... ?
                mShadowHullVerticesNum++;
            }

            //// Add the indicies to the buffer
            foreach (int index in hull.Indicies)
            {
                mShadowHullIndicies[mShadowHullIndiciesNum] = vertexCount + index; // what about this? Add range?
                mShadowHullIndiciesNum++;
            }
        }

        public void ClearHullArrays()
        {
            mShadowHullIndiciesNum = 0;
            mShadowHullVerticesNum = 0;
        }

        public void DrawSquareQuad(Vector2 position, float rotation, float size, Color color)
        {
            size /= 2;

            size = (float)Math.Sqrt(Math.Pow(size, 2) + Math.Pow(size, 2));

            rotation += (float)Math.PI / 4;

            var cos = (float)Math.Cos(rotation) * size;
            var sin = (float)Math.Sin(rotation) * size;

            var v1 = new Vector3(+cos, +sin, 0) + new Vector3(position, 0);
            var v2 = new Vector3(-sin, +cos, 0) + new Vector3(position, 0);
            var v3 = new Vector3(-cos, -sin, 0) + new Vector3(position, 0);
            var v4 = new Vector3(+sin, -cos, 0) + new Vector3(position, 0);

            quad[0] = new VertexPositionColorTexture()
            {
                Position = v2,
                Color = color,
                TextureCoordinate = new Vector2(0, 0),
            };

            quad[1] = new VertexPositionColorTexture()
            {
                Position = v1,
                Color = color,
                TextureCoordinate = new Vector2(1, 0),
            };

            quad[2] = new VertexPositionColorTexture()
            {
                Position = v3,
                Color = color,
                TextureCoordinate = new Vector2(0, 1),
            };

            quad[3] = new VertexPositionColorTexture()
            {
                Position = v4,
                Color = color,
                TextureCoordinate = new Vector2(1, 1),
            };

            this.mGraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleStrip, quad, 0, 2);
        }

        public void DrawClippedFov(Vector2 position, float rotation, float size, Color color, float fov)
        {
            fov = MathHelper.Clamp(fov, 0, MathHelper.TwoPi);

            if (fov == 0)
            {
                return;
            }
            else if (fov == MathHelper.TwoPi)
            {
                this.DrawSquareQuad(position, rotation, size, color);
                return;
            }
            else
            {
                var ccw = ClampToBox(fov / 2);
                var cw = ClampToBox(-fov / 2);

                var ccwTex = new Vector2(ccw.X + 1, -ccw.Y + 1) / 2f;
                var cwTex = new Vector2(cw.X + 1, -cw.Y + 1) / 2f;

                #region Vertices

                vertices[0] = new VertexPositionColorTexture()
                {
                    Position = Vector3.Zero,
                    Color = color,
                    TextureCoordinate = new Vector2(0.5f, 0.5f),
                };

                vertices[1] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(ccw,0),
                    Color = color,
                    TextureCoordinate = ccwTex,
                };

                vertices[2] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(-1, 1, 0),
                    Color = color,
                    TextureCoordinate = new Vector2(0, 0),
                };

                vertices[3] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(1, 1, 0),
                    Color = color,
                    TextureCoordinate = new Vector2(1, 0),
                };

                vertices[4] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(1, -1, 0),
                    Color = color,
                    TextureCoordinate = new Vector2(1, 1),
                };

                vertices[5] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(-1, -1, 0),
                    Color = color,
                    TextureCoordinate = new Vector2(0, 1),
                };

                vertices[6] = new VertexPositionColorTexture()
                {
                    Position = new Vector3(cw, 0),
                    Color = color,
                    TextureCoordinate = cwTex,
                };

                var r = Matrix.CreateRotationZ(rotation) * Matrix.CreateScale(size / 2) * Matrix.CreateTranslation(new Vector3(position, 0));

                for (int i = 0; i < vertices.Length; i++)
                {
                    var vertex = vertices[i];

                    Vector3.Transform(ref vertex.Position, ref r, out vertex.Position);

                    vertices[i] = vertex;
                }

                #endregion Vertices

                Int32[] indicies;

                #region Indicies

                if (fov <= MathHelper.Pi / 2)
                {
                    indicies = indicies3;
                }
                else if (fov <= 3 * MathHelper.Pi / 2)
                {
                    indicies = indicies9;
                }
                else
                {
                    indicies = indicies15;
                }
                #endregion Indicies

                this.mGraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indicies, 0, indicies.Length / 3);
            }
        }

        public static Vector2 ClampToBox(float angle)
        {
            var x = Math.Cos(angle);
            var y = Math.Sin(angle);
            var absMax = Math.Max(Math.Abs(x), Math.Abs(y));

            return new Vector2((float)(x / absMax), (float)(y / absMax));
        }

        public void BufferDraw()
        {
            if (this.mShadowHullIndiciesNum >= 3)
            {
                this.mGraphicsDevice.DrawUserIndexedPrimitives<ShadowHullVertex>(PrimitiveType.TriangleList, this.mShadowHullVertices, 0, this.mShadowHullVerticesNum, this.mShadowHullIndicies, 0, this.mShadowHullIndiciesNum / 3);
            }
        }

        public void DrawFullscreenQuad()
        {
            // Obtain the original rendering states
            var originalRasterizerState = this.mGraphicsDevice.RasterizerState;

            // Draw the quad
            this.mEffect.CurrentTechnique = this.mEffect.Techniques["ScreenCopy"];
            //this.mGraphicsDevice.RasterizerState = RasterizerState.CullNone;

            this.mEffect.Parameters["TexelBias"].SetValue(new Vector2(0.5f / this.mGraphicsDevice.Viewport.Width, 0.5f / this.mGraphicsDevice.Viewport.Height));

            foreach (var effectPass in this.mEffect.CurrentTechnique.Passes)
            {
                effectPass.Apply();
                this.mGraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, KryptonRenderHelper.UnitQuad, 0, 2);
            }

            // Reset to the original rendering states
            //this.mGraphicsDevice.RasterizerState = originalRasterizerState;
        }

        public void BlurTextureToTarget(Texture2D texture, LightMapSize mapSize, BlurTechnique blurTechnique, float bluriness)
        {
            // Get the pass to use
            string passName = "";

            switch (blurTechnique)
            {
                case (BlurTechnique.Horizontal):
                    this.mEffect.Parameters["BlurFactorU"].SetValue(1f / this.GraphicsDevice.PresentationParameters.BackBufferWidth);
                    passName = "HorizontalBlur";
                    break;

                case (BlurTechnique.Vertical):
                    this.mEffect.Parameters["BlurFactorV"].SetValue(1f / this.mGraphicsDevice.PresentationParameters.BackBufferHeight);
                    passName = "VerticalBlur";
                    break;
            }

            var biasFactor = KryptonRenderHelper.BiasFactorFromLightMapSize(mapSize);

            // Calculate the texel bias
            Vector2 texelBias = new Vector2()
            {
                X = biasFactor / this.mGraphicsDevice.Viewport.Width,
                Y = biasFactor / this.mGraphicsDevice.Viewport.Height,
            };


            this.mEffect.Parameters["Texture0"].SetValue(texture);
            this.mEffect.Parameters["TexelBias"].SetValue(texelBias);
            this.mEffect.Parameters["Bluriness"].SetValue(bluriness);
            this.mEffect.CurrentTechnique = this.mEffect.Techniques["Blur"];

            mEffect.CurrentTechnique.Passes[passName].Apply();
            this.mGraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, KryptonRenderHelper.UnitQuad, 0, 2);
        }

        public void DrawTextureToTarget(Texture2D texture, LightMapSize mapSize, BlendTechnique blend)
        {
            // Get the technique to use
            string techniqueName = "";

            switch (blend)
            {
                case(BlendTechnique.Add):
                    techniqueName = "TextureToTarget_Add";
                    break;

                case(BlendTechnique.Multiply):
                    techniqueName = "TextureToTarget_Multiply";
                    break;
            }

            var biasFactor = KryptonRenderHelper.BiasFactorFromLightMapSize(mapSize);

            // Calculate the texel bias
            Vector2 texelBias = new Vector2()
            {
                X = biasFactor / this.mGraphicsDevice.ScissorRectangle.Width,
                Y = biasFactor / this.mGraphicsDevice.ScissorRectangle.Height,
            };

            this.mEffect.Parameters["Texture0"].SetValue(texture);
            this.mEffect.Parameters["TexelBias"].SetValue(texelBias);
            this.mEffect.CurrentTechnique = this.mEffect.Techniques[techniqueName];

            // Draw the quad
            foreach (var effectPass in this.mEffect.CurrentTechnique.Passes)
            {
                effectPass.Apply();
                this.mGraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, KryptonRenderHelper.UnitQuad, 0, 2);
            }
        }

        private static float BiasFactorFromLightMapSize(LightMapSize mapSize)
        {
            switch (mapSize)
            {
                case (LightMapSize.Full):
                    return 0.5f;

                case (LightMapSize.Fourth):
                    return 0.6f;

                case (LightMapSize.Eighth):
                    return 0.7f;

                default:
                    return 0.0f;
            }
        }

        public void BufferAddBoundOutline(Common.BoundingRect boundingRect)
        {
            var vertexCount = this.mShadowHullVerticesNum;

            this.mShadowHullVertices[mShadowHullVerticesNum] = (new ShadowHullVertex()
            {
                Color = Color.Black,
                Normal = Vector2.Zero,
                Position = new Vector2(boundingRect.Left, boundingRect.Top)
            });
            mShadowHullVerticesNum++;

            this.mShadowHullVertices[mShadowHullVerticesNum] = (new ShadowHullVertex()
            {
                Color = Color.Black,
                Normal = Vector2.Zero,
                Position = new Vector2(boundingRect.Right, boundingRect.Top)
            });
            mShadowHullVerticesNum++;

            this.mShadowHullVertices[mShadowHullVerticesNum] = (new ShadowHullVertex()
            {
                Color = Color.Black,
                Normal = Vector2.Zero,
                Position = new Vector2(boundingRect.Right, boundingRect.Bottom)
            });
            mShadowHullVerticesNum++;

            this.mShadowHullVertices[mShadowHullVerticesNum] = (new ShadowHullVertex()
            {
                Color = Color.Black,
                Normal = Vector2.Zero,
                Position = new Vector2(boundingRect.Left, boundingRect.Bottom)
            });
            mShadowHullVerticesNum++;

            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 0);
            mShadowHullIndiciesNum++;
            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 1);
            mShadowHullIndiciesNum++;
            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 2);
            mShadowHullIndiciesNum++;

            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 0);
            mShadowHullIndiciesNum++;
            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 2);
            mShadowHullIndiciesNum++;
            this.mShadowHullIndicies[mShadowHullIndiciesNum] = (vertexCount + 3);
            mShadowHullIndiciesNum++;
        }
    }
}

Additionally, in the Light2D class, inside the Draw method, you'll need to change the line that says:

 

helper.ShadowHullVertices.Clear();
helper.ShadowHullIndicies.Clear();

 

For:

 

helper.ClearHullArrays();

Now it generates a lot less garbage (there's still some, but not alarming).

quad = new VertexPositionColorTexture[]
Coordinator
Jun 9, 2011 at 4:27 PM

Thank you, sir!

I'm not sure when I can get around to adding this to the official release, but I'm thankful you've submitted it, so that others might benefit from it as well :)

Jun 10, 2011 at 10:44 AM

You are welcome, I'm glad I can help somehow (I'm waiting to achieve the game release for helping through donation :) ).

Remember that if someone expects to have A LOT of shadow hulls, should modify the constant MAX_HULLS_VERTEX to a higher value (1000 works good enough for me for now).

Jun 16, 2011 at 6:58 PM

Nice thing, thanks ;)

When i implemented Krypton into my engine, i saw that there is a huge performance problem within the draw-method.
I didn´t know how to fix, but now you did it :)

 

Thanks :) Hopefully Krypton 3 comes soon :D

Coordinator
Aug 6, 2011 at 8:34 PM

I'll try to get this added into the source asap. As usual, I may (if applicable) change naming schemes, etc, to match Krypton's source.