Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

Batching 101

Batching 101

Scet
The Tub of Awesome · · 6 min read
425 0
Alright here it is, my code to batch all the objects in id.Net, along with the long ass explanation of how it works.

using System;using Drawing = System.Drawing;using Collections = System.Collections.Generic;using DirectX = Microsoft.DirectX;using Direct3D = Microsoft.DirectX.Direct3D;namespace id{	public class Scene	{				private class Batch		{							public Collections.List IndexList = new Collections.List();			public int MinimumIndex = int.MaxValue;			public int Offset = 0;						public int Vertices = 0;						public Batch()			{				return;			}						public void Initialize()			{				int MaximimIndex = int.MinValue;				for( int i = 0; i < IndexList.Count; i++ )				{					if( IndexList < MinimumIndex )					{						MinimumIndex = (int)IndexList;					}					if( IndexList > MaximimIndex )					{						MaximimIndex = (int)IndexList;					}				}				Vertices = ( MaximimIndex - MinimumIndex ) + 1;				return;			}					}				public class Object		{						public int BufferOffset = 0;						public Graphic Graphic = null;	  				public uint[] IndexArray = null;					  	public Direct3D.CustomVertex.PositionTextured[] VertexArray = null;			public Object()			{				return;			}			public Object( Graphic GraphicReference )			{				Graphic = GraphicReference;				return;			}				}				private Direct3D.IndexBuffer IndexBuffer = null;		private Collections.Dictionary BatchList = new Collections.Dictionary();		public static DirectX.GraphicsStream GraphicsStream = null;		private Collections.Dictionary> ObjectList = new Collections.Dictionary>();		private static Collections.List VertexList = new Collections.List();			private static Direct3D.VertexBuffer VertexBuffer = null;		static Scene()		{			return;		}				public void Add( Object ObjectReference )		{			if( ObjectReference == null )			{				return;			}			else if( ( ObjectReference.Graphic == null ) || ( ObjectReference.VertexArray == null ) || ( ObjectReference.VertexArray.Length == 0 )  || ( ObjectReference.IndexArray == null ) || ( ObjectReference.IndexArray.Length == 0 ) )			{				return;			}						else if( ObjectList.ContainsKey( ObjectReference.Graphic ) == false )			{				ObjectList.Add( ObjectReference.Graphic, new Collections.List() );				BatchList.Add( ObjectReference.Graphic, new Batch() );			}			ObjectList[ObjectReference.Graphic].Add( ObjectReference );			return;		}		public void Build()		{			int Indices = 0;			int BaseIndex = 0;			Collections.IEnumerator>> ObjectListEnumerator = ObjectList.GetEnumerator();			do			{				BaseIndex = 0;				if( ( ObjectListEnumerator.Current.Key != null ) && ( ObjectListEnumerator.Current.Value != null ) )				{					BatchList[ObjectListEnumerator.Current.Key].Offset = VertexList.Count;					for( int i = 0; i < ObjectListEnumerator.Current.Value.Count; i++ )					{							for( int j = 0; j < ObjectListEnumerator.Current.Value.IndexArray.Length; j++ )						{							ObjectListEnumerator.Current.Value.IndexArray[j] += (uint)BaseIndex;						}						Indices += ObjectListEnumerator.Current.Value.IndexArray.Length;						BaseIndex += ObjectListEnumerator.Current.Value.VertexArray.Length; 						ObjectListEnumerator.Current.Value.BufferOffset = VertexList.Count;						VertexList.AddRange( ObjectListEnumerator.Current.Value.VertexArray );						BatchList[ObjectListEnumerator.Current.Key].IndexList.AddRange( ObjectListEnumerator.Current.Value.IndexArray );					}				}			}			while( ObjectListEnumerator.MoveNext() );			if( Indices > 0 )			{				int IndexBufferOffset = 0;				IndexBuffer = new Direct3D.IndexBuffer( typeof( uint ), Indices, Game.Device, Direct3D.Usage.None, Direct3D.Pool.Managed );				Collections.IEnumerator> BatchListEnumerator = BatchList.GetEnumerator();				do				{					if( ( BatchListEnumerator.Current.Key != null ) && ( BatchListEnumerator.Current.Value != null ) )					{						IndexBuffer.SetData( BatchListEnumerator.Current.Value.IndexList.ToArray(), IndexBufferOffset, Direct3D.LockFlags.None );						IndexBufferOffset += BatchListEnumerator.Current.Value.IndexList.Count * sizeof( uint );						BatchListEnumerator.Current.Value.Initialize();					}				}				while( BatchListEnumerator.MoveNext() );			}			return;		}				public static void BuildBuffer()		{			VertexBuffer = new Direct3D.VertexBuffer( typeof( Direct3D.CustomVertex.PositionTextured ), VertexList.Count, Game.Device, Direct3D.Usage.SoftwareProcessing, Direct3D.CustomVertex.PositionTextured.Format, Direct3D.Pool.Managed );			VertexBuffer.SetData( VertexList.ToArray(), 0, Direct3D.LockFlags.None );			VertexList.Clear();			return;		}				public static void Lock()		{			GraphicsStream = VertexBuffer.Lock( 0, 0, Direct3D.LockFlags.None );			return;		}				public void Render()		{			int Position = 0;			Game.Device.VertexFormat = Direct3D.CustomVertex.PositionTextured.Format;			Game.Device.Indices = IndexBuffer;			Game.Device.SetStreamSource( 0, VertexBuffer, 0 );			Collections.IEnumerator> BatchListEnumerator = BatchList.GetEnumerator();			do			{				if( ( BatchListEnumerator.Current.Key != null ) && ( BatchListEnumerator.Current.Value != null ) )				{						BatchListEnumerator.Current.Key.Apply();					Game.Device.DrawIndexedPrimitives( Direct3D.PrimitiveType.TriangleList, BatchListEnumerator.Current.Value.Offset, BatchListEnumerator.Current.Value.MinimumIndex, BatchListEnumerator.Current.Value.Vertices, Position, BatchListEnumerator.Current.Value.IndexList.Count / 3 );					Position += BatchListEnumerator.Current.Value.IndexList.Count;				}			}			while( BatchListEnumerator.MoveNext() );			return;		}				public static void Unlock()		{			VertexBuffer.Unlock();			return;		}			}	}


Classes:

	Object - This is inherited by anything that has to be added to a 				 scene, which is walls and floor/ceiling polygons.		BufferOffset -  This is where it's vertices got added to the 						VertexBuffer, used so moving sectors can be 						rebuilt easily.		Graphic - The texture or animation used for is object, for 	  		          sorting.		IndexArray - Indices into the VertexBuffer. Each object only		             needs to start as though it was from 0, the scene 		                             builder will find and assign the actually indices                                     			     when built. These need to be in order though for 			     		     batching to work(you can't have 0,2,3,5).		VertexArray - The vertices.	Batch - This is used to store the information needed for the call to 				DrawIndexedPrimitives. There is one batch for evey different		texture in a scene.		IndexList - Contains all the indices used for this batch. Used 					    to find the other values.		MinimumIndex - The lowest index out of all the indices that make 			       up this batch, for MinIndex.		Offset - The value used for BaseVertexIndex, takes the number of 			 vertics in the level at the start of being "built".					Vertices - The number of different vertices, for NumVertices;	Scene - The actual scene class. Inherited by each sector, but also 				stores the vertex data for the whole level.		IndexBuffer - The index buffer for this sector.		MinimumIndex - List of batches, one batch per texture in the 					       sector.		GraphicsStream - Walls use this to add their new vertices to the  				 VertexBuffer when being rebuilt.		ObjectList - List of objects(walls,polygons) arranged by 					     texture.		VertexList - Stores the vertices before building the 						     VertexBuffer.			VertexBuffer - Vertex buffer for the whole level.		Add - Adds an object to ObjectList if it's not in there already. 		      Also adds a batch based on the objects texture if one 				      doesn't already exist.		Build - This starts off by going through all the different 		      		        textures in the sector. The batch for that texture has       		        it's offset assigned to the length of VertexIndex(if the       		        fisrt batch of objects contains 100 vertices, then this       		        becomes 100 for the second batch). Then there's a loop       		        that goes through all the objects that have this       		      		        texture. The object's have their indices and offset into       		        the vertex buffer set to the proper values. Then the       		        object's have their vertices added to VertexList and       		        their indices added into the current batch.       		        Next after all that is done and the batches have all       		        their indices, a loop adds those indices to the index       		        buffer. Now the index buffer is arranged by texture.		BuildBuffer - This is the last functioned called before doing 		        		      any rendering, it loads all the vertices from 		        		      VertexList into VertexBuffer.		Render - Goes through all the batches in this scene, setting the         		         right texture and rendering based on the indices. Putting it all together: 	1. When loading a level, the sectors are loaded first, although at this 		   time they don't contain much information(only that of the WAD).	2. When loading the lines each checks what sector it belongs in(based on 	   which side falls into "the void"). The line and it's 				   walls(lower,middle,upper) are then added to that sector(Scene.Add).	3. Once the level has all the data from the Doom WAD file, it loops 			   through all the sectors calling Build.	4. After all the sectors are "built", Scene.BuildBuffer is called




That's it for now. Hopefully someone will find it somewhat usefull, it sure took a long time to write. I have tested it with some moving sectors and all the values do come out to be the right value.

Discussion

Loading comments...