This project is read-only.

DynamicVertexBuffer performance issues on ATI GPUs

Mar 17, 2012 at 4:45 AM

Hi,

I'd like to just point this out as it was somewhat of a pain for us to figure out the "fix" for an ATI specific issue we ran into.

In our game, we have a 400K poly mesh that was using a dynamic vertex buffer. My colleagues ATI GPU was having serious issues getting a good performance when this mesh was being rendered while my Nvidia GPU was breezing thru it (speaking in relative terms of performance).

The problem came down to the fact that ATI is doing some seriously wasteful work behind the scenes when using a DynamicVertexBuffer. Since we only really update the positions and normals on our 400K mesh, I decided to stop using the DynamicVertexBuffer and switch to using a regular VertexBuffer and just update the data using SetData (had to extend Xen to add this support).

The results of this change were interesting but also expected. While the fps didn't nudge on Nvidia, the ATI GPU performance almost doubled. I am probably going to post this on the XNA forum as well because this isn't so much a Xen problem as it is an ATI driver issue and XNA might want to know about it as well.

Cheers.

Jul 3, 2012 at 12:52 AM
Edited Jul 3, 2012 at 12:54 AM

Thanks for the heads up! Noticed some differences as well on different machines using DynamicVertexBuffer.

Aug 12, 2012 at 9:48 AM

How did you extend Xen to add support for the SetData on xen? and change it from DynamicVertexBuffer to VertexBuffer?

I tried the following, using the WriteBuffer method which seams like the proper way to use Implementation, but it doesn't seem change the underlying vertexbuffer.

        public void SetData(Application app, int offset, VertexType[] data, int start, int count, int stride)
        {
            buffer.WriteBuffer(app, start, count * stride, vb, data);
            buffer.AddDirtyRange(start, count, vb.GetType(), false);
        }

Aug 17, 2012 at 12:45 AM
Edited Aug 17, 2012 at 12:50 AM

I added support to convert a given vertex buffer in a model from the static vertex data to a dynamic vertex buffer on the fly.

Here's some of the details:

In ModelContent.cs in GeometryData class added the following function:

 

        /// <summary>
        /// Converts the Vertices reference to be a dynamic buffer and returns the contents in "vertexData"
        /// NOTE: There is very little error checking, so use with caution
        /// </summary>
        /// <typeparam name="T">The struct which represents the vertex format</typeparam>
        /// <param name="vertexData">The array which will contain the data. Make it is large enough to hold all of the vertex data</param>        
        /// <param name="usageType"></param>
        public void MakeVerticesDynamic<T>(T[] vertexData, ResourceUsage usageType) where T : struct
        {
            if (!vertices.TryExtractAllVertexData(vertexData))
            {
                vertices = new Vertices<T>(vertexData);
                vertices.ResourceUsage = usageType;
            }

        }

 

In Vertices.cs under the class Vertices<VertexType> I added the following function:

 

        /// <summary>
        /// Returns the vertex buffer data for every channel used
        /// </summary>
        /// <typeparam name="T">The VertexType of the vertex data</typeparam>
        /// <param name="output">True if the data is available in Vertices class. False if it has to get it from the internal XNA VertexBuffer class</param>
        /// <returns></returns>
        bool IVertices.TryExtractAllVertexData<T>(T[] output)
        {
            if (output == null || output.Length < this.Count)
                throw new ArgumentException("output data array is either null or too small");

            if(resUsage == Graphics.ResourceUsage.Dynamic)
            {
                if (buffer.Data is T[])
                {
                    output = buffer.Data as T[];
                }
                else
                {
                    throw new ArgumentException("Provided buffer is not of proper VertexType");
                }
                return true;
            }
            else
            {
                if (vb == null) throw new InvalidOperationException("Vertex data must be warmed before calling TryExtractAllVertexData");
                vb.GetData<T>(output);
                return false;
            }
        }

 

Which also means that I added the function to the IVertices interface and put a dummy implementation into the other IVertices using classes since I don't use those.

In Resource.cs I added a new entry to the ResourceUsage enum like the following:

 

        /// Useful for cases where the number of elements in the vertex buffer do not change, but the values for the element do (such as vert based animation)
        /// The data is "Readable", hence the value includes Readable
        /// </summary>
        DynamicValueStaticSize = 8 | Readable,

 

Updated ValidateDirty() in Vertices.cs to be:

 

		void ValidateDirty()
		{
			if (((resUsage & ResourceUsage.Dynamic) == 0) &&
                            ((resUsage & ResourceUsage.DynamicValueStaticSize) == 0))
				throw new InvalidOperationException("this.ResourceUsage lacks ResourceUsage.Dynamic flag");
		}

 

And the following part:

 

				if ((resUsage & ResourceUsage.Dynamic) != ResourceUsage.Dynamic)
					buffer.ClearBuffer();

 

To be:

 

				if (((resUsage & ResourceUsage.Dynamic) != ResourceUsage.Dynamic) &&
                                    ((resUsage & ResourceUsage.DynamicValueStaticSize) != ResourceUsage.DynamicValueStaticSize))
					buffer.ClearBuffer();

 

So after that, then in your game code you can do the something like the following (as an example) once when the model data is loaded (assumes that the model is of a single instance otherwise you will modify every single modelinstance as it's being rendered):

public struct MyVertexFormat
{
    public Vector3 Position;
    public Color Color;
    public Vector2 TextureCoordinate;
    public Vector3 Normal;
}

MyVertexFormat[][][] vertData;

In an init function:

            vertData = new MyVertexFormat[6][];

            for (int i = 0; i < 6; i++)
            {
                GeometryData geomData = renderObj.Model.ModelData.Meshes[i].Geometry[0];
                vertData[i] = new MyVertexFormat[geomData.Vertices.Count];
                geomData.MakeVerticesDynamic(vertData[i], ResourceUsage.DynamicValueStaticSize);
                geomData.Vertices.Warm(state);
            }

What happens there is that under the hood, Xen will extract the packed vertex buffer data and pull it out into the array you pass in. From that point on, any time you call something like:

 

renderObj.Model.ModelData.Meshes[face].Geometry[geomIndex].Vertices.SetDirty();

 

The next time you call draw, Xen will copy over the data from "vertData" and then draw the vertex buffer.

Alternatively, when initing the vertex buffer, instead of using ResourceUsage.DynamicValueStaticSize, if you set the flag as ResourceUsage.Dynamic, then Xen shoud create a DynamicVertexBuffer instead of the regular VertexBuffer. That said, I don't remember if i had tested the code with that, so I can't say for sure. Also I don't know if I have properly posted all the necessary code changes, so expect to see some odd and ends that you might need to plug up with these code changes.