   [C#] Batching with multiple textures.  Multiple dynamic vertex buffers required?
  I'm trying to figure out a way to batch draw quadrangles and use more than one texture.Am I right in thinking that I will need to have a separate draw call for each texture?If so, I thought about grouping all quadrangle vertices by texture in a dictionary:Dictionary<Texture2D, List<VertexQuadBatch>> quads_Dictionary = new Dictionary<Texture2D, List<VertexQuadBatch>>(16);I can then add the current quadrangle vertices to the right key in the dictionary based on its texture:   public void AddToBatch(Quadrangle quad)   {  if (!quads_Dictionary.ContainsKey(quad.Texture))  { quads_Dictionary.Add(quad.Texture, new List<VertexQuadBatch>(Settings.QUAD_MAX));  }  quads_Dictionary[quad.Texture].AddRange(quad.Vertices);   }This meant I had to make some changes to my usual Draw() method so it now looks like the following:   private void DrawQuadrangleBatch()   {  foreach (Texture2D texture in quads_Dictionary.Keys)  { int quad_Count = quads_Dictionary[texture].Count / 4; if (quad_Count == 0) {continue; } int vertex_Index = quad_Count * 4; int index_Index = quad_Count * 6; for (int i = 0; i < quads_Dictionary[texture].Count; ++i) {vertices_Quads = quads_Dictionary[texture]; }  effect_QuadrangleBatch.Parameters["Texture0"].SetValue(texture); vb.SetData<VertexQuadBatch>(vertices_Quads, 0, vertex_Index); GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexQuadBatch.SizeInBytes); GraphicsDevice.Indices = ib; GraphicsDevice.VertexDeclaration = vd_VertexQuadrangleInstancing; effect_QuadrangleBatch.Begin(); effect_QuadrangleBatch.CurrentTechnique.Passes[0].Begin(); // DrawIndexedPrimitives should be used otherwise DynamicVertexBuffer and IndexBuffer aren't used GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,   // TriangleStrip can't be used as quads aren't always joined together0,0,vertex_Index,0,index_Index / 3// Divide the index count by 3 because 3 indices make 1 triangle ); effect_QuadrangleBatch.CurrentTechnique.Passes[0].End(); effect_QuadrangleBatch.End(); // Clear the quadrilateral list associated with the current texture for this frame quads_Dictionary[texture].Clear();  }   }The problem is that I have to copy the vertices to the main vertices in the dynamic buffer, which is slow: for (int i = 0; i < quads_Dictionary[texture].Count; ++i) {vertices_Quads = quads_Dictionary[texture]; }The real problem I am facing though is the fact that:vb.SetData<VertexQuadBatch>(vertices_Quads, 0, vertex_Index);can't be set in a foreach loop, otherwise I get the following error:Quote:The operation was aborted. You may not modify a resource that has been set on a device, or after it has been used within a tiling bracket.Does this mean I will have to have a separate dynamic vertex buffer for each texture?How the heck is that going to work when I'm not sure just how many textures there might be.Has anyone got any suggestions on how to tackle this problem, or perhaps suggest another method that I can use to batch draw quadrangles with multiple textures?Thank you.   
  If your quads are grouped by their texture, then you can still use just the one vertex and index buffer, and get away with just multiple GraphicsDevice.DrawIndexedPrimitives() calls, by supplying an appropriate start index and primitive count. This way you just switch textures between each draw call.   ;Quote:Original post by Spa8nkyI'm trying to figure out a way to batch draw quadrangles and use more than one texture.Am I right in thinking that I will need to have a separate draw call for each texture?Yep, but you could do some preprocessing to reduce the number of used textures. Texture atlas is one option (putting many textures on one) or maybe texture arrays.Quote:Original post by Spa8nkyIf so, I thought about grouping all quadrangle vertices by texture in a dictionary:To be honest, I tried this once, but it failed for me. The problem is, that with multitexturing you got many texture combinations, too many for my taste. Quote:Original post by Spa8nkyThe problem is that I have to copy the vertices to the main vertices in the dynamic buffer, which is slow:You have different goals here. You have textures, shaders and geometry which can be used as "batching" criteria. In my opinion shaders are the most important, then textures and then geometry. So, why do you want to copy vertices into one single dynamic buffer, which needs to be uploaded after construction, just to save some draw calls ? I would think that switching between static geometry buffers, which are already in video memory, are faster than building and uploading a dynamic buffer (each frame?).Quote:Original post by Spa8nkyHow the heck is that going to work when I'm not sure just how many textures there might be.Make your life simpler by grouping first by shader, then sort, but don't group, by textures and don't construct larger batches for geometry. IMHO the only reason to build larger batches is for large amounts of very low poly geometry (i.e. grass), which will be dynamically constructed once(!) when it is inside visible range.;Quote:Original post by ExorcistIf your quads are grouped by their texture, then you can still use just the one vertex and index buffer, and get away with just multiple GraphicsDevice.DrawIndexedPrimitives() calls, by supplying an appropriate start index and primitive count. This way you just switch textures between each draw call.Aha!  It's late night here but I think I understand what you are saying.I will only need to set the dynamic vertex buffer once, but how can I be sure that all vertices are grouped together by texture?I can still only think of using a Dictionary but perhaps instead of using a lists associated with the textures, I could use arrays?Each array associated with each texture would need to have an separate index count so I could copy the correct vertices to the vertex buffer and draw at the correct start index.How would you guys suggest implementing that?   ;  Personally i wouldn't use Dictionary<Texture, Quad[]>, but instead just have the Quad array kept sorted at all times. If you only ever sort on Insert, it should be quick enough to do.   ;Quote:Original post by ExorcistPersonally i wouldn't use Dictionary<Texture, Quad[]>, but instead just have the Quad array kept sorted at all times. If you only ever sort on Insert, it should be quick enough to do.Does this mean that I should have two arrays, one that stores quads (sorted by texture) and the array that stores the quad vertices?I will need the the quad.Texture to sort an array by texture so I can't see a way of just having the one array of vertices, as there will be no comparer to sort by otherwise.  Is this correct?   ;  EDIT: Iganore this, solution below is better IMO.   ;  Alternatively, a solution that avoids multiple vertex buffers would be to have something similar to the following:Texture[] textures;int[] startIndexes;int[] quadCounts;QuadVertices[] vertices;DynamicVertexBuffer vertexBuffer;Then as was mentioned by a poster above, keep the vertices array sorted at all times by texture (just make your insert/remove code use the data from startIndexes and quadCounts) and draw a part of the vertex buffer for each texture (again, using startIndexes and quadCounts).   ;  Ok guys, what do you think of the following solution?   private void DrawQuadrangleBatch()   {  Game1.Instance.count = quadrangles_Index;  if (quadrangles_Index == 0)  { return;  }  Texture2D texture = quadrangles[0].Texture;   // Current quadrangle texture (from very first quad)  List<int> texture_Index = new List<int>(16);  // Store the vertex index at which the texture changes  texture_Index.Add(0);// Add the first index  int vertex_Index = 0;  // For all quadrangles added this frame  for (int i = 0; i < quadrangles_Index; ++i)  { Quadrangle quad = quadrangles; if (quad.Texture != texture) {// Update the reference to the texture now that it has changedtexture = quad.Texture;// Add the vertex index at which it changedtexture_Index.Add(vertex_Index); } vertices_Quads[vertex_Index++] = quad.Vertices[0]; vertices_Quads[vertex_Index++] = quad.Vertices[1]; vertices_Quads[vertex_Index++] = quad.Vertices[2]; vertices_Quads[vertex_Index++] = quad.Vertices[3];  }  vb.SetData<VertexQuadBatch>(vertices_Quads, 0, vertex_Index);  GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexQuadBatch.SizeInBytes);  GraphicsDevice.Indices = ib;  GraphicsDevice.VertexDeclaration = vd_VertexQuadrangleInstancing;  // Change textures and batch draw by texture  for (int i = 0; i < texture_Index.Count; ++i)  { int index_Start = texture_Index; int index_End = i + 1 == texture_Index.Count ? vertex_Index : texture_Index;<br> <span class="cpp-keyword">int</span> numVertices = index_End - index_Start;<br><br> <span class="cpp-comment">// Get the texture from the first quadrangle with the new texture</span><br> effect_QuadrangleBatch.Parameters[<span class="cpp-literal">"Texture0"</span>].SetValue(quadrangles[index_Start / <span class="cpp-number">4</span>].Texture);<br><br> effect_QuadrangleBatch.Begin();<br> effect_QuadrangleBatch.CurrentTechnique.Passes[<span class="cpp-number">0</span>].Begin();<br><br> <span class="cpp-comment">// DrawIndexedPrimitives should be used otherwise DynamicVertexBuffer and IndexBuffer aren't used</span><br> GraphicsDevice.DrawIndexedPrimitives(<br>PrimitiveType.TriangleList,    <span class="cpp-comment">// TriangleStrip can't be used as quads aren't always joined together</span><br>index_Start,<br><span class="cpp-number">0</span>,<br>numVertices,<br><span class="cpp-number">0</span>,<br>numVertices / <span class="cpp-number">2</span> <span class="cpp-comment">// Divide the vertex count by 2 because 4 vertices make 2 triangles (always min of 4 vertices)</span><br>);<br><br> effect_QuadrangleBatch.CurrentTechnique.Passes[<span class="cpp-number">0</span>].End();<br> effect_QuadrangleBatch.End();<br>  }<br><br>  <span class="cpp-comment">// Clear the quadrilateral list for this frame now the quad has been drawn</span><br>  quadrangles_Index = <span class="cpp-number">0</span>;<br>   }<br><br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>What I am doing is storing the value of the vertex index whenever the texture changes into a texture index list.<br><br>I then iterate through the texture index list and calculate the start and end vertex indices based off the stored texture index values.<br><br>Does this look like a sensible idea to you lot?<br><br>EDIT: Updated and all working correctly!<br><br>EDIT2: I've tweaked the draw method so arrays are used instead of lists and improved speed a little.  I can post the differences if anyone is interested.<br><br><!–EDIT–><span class=editedby><!–/EDIT–>[Edited by - Spa8nky on April 9, 2010 3:28:52 PM]<!–EDIT–></span><!–/EDIT–>;  For your purpose it looks good to me :)   