// Project: XnaShooter, File: MeshRenderManager.cs // Namespace: XnaShooter.Graphics, Class: RenderableMesh // Path: C:\code\XnaShooter\Graphics, Author: Abi // Code lines: 573, Size of file: 19,90 KB // Creation date: 26.10.2006 22:48 // Last modified: 03.11.2006 09:22 // Generated with Commenter by abi.exDream.com #region Using directives using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Text; using XnaShooter.Game; using XnaShooter.Helpers; using XnaShooter.Shaders; #endregion namespace XnaShooter.Graphics { /// /// Mesh render manager, a little helper class which allows us to render /// all our models with much faster performance through sorting by /// material and shader techniques. /// /// We keep a list of lists of lists for rendering our RenderableMeshes /// sorted by techniques first and materials second. /// The most outer list contains all techniques (see /// MeshesPerMaterialPerTechniques). /// Then the first inner list contains all materials (see MeshesPerMaterial). /// And finally the most inner list contains all meshes that use the /// technique and material we got. /// Additionally we could also sort by shaders, but since all models /// use the same shader (normalMapping), that is the only thing we support /// here. Improving it to support more shaders is easily possible. /// /// All this is created in the Model constructor. At runtime we just /// go through these lists and render everything down as quickly as /// possible. /// public class MeshRenderManager { #region Remember vertex and index buffers /// /// Don't set vertex and index buffers again if they are already set this /// frame. /// static VertexBuffer lastVertexBufferSet = null; static IndexBuffer lastIndexBufferSet = null; #endregion #region RenderableMesh helper class /// /// Renderable mesh, created in the Model constructor and is rendered /// when we render all the models at once at the end of each frame! /// public class RenderableMesh { #region Variables /// /// Vertex buffer /// public VertexBuffer vertexBuffer; /// /// Index buffer /// public IndexBuffer indexBuffer; /// /// Material /// public Material material; /// /// Used technique /// public EffectTechnique usedTechnique; /// /// Vertex declaration /// public VertexDeclaration vertexDeclaration; /// /// Stream offset, vertex stride, etc. /// All parameters we need for rendering. /// public int streamOffset, vertexStride, baseVertex, numVertices, startIndex, primitiveCount; /// /// List of render matrices we use every frame. At creation time /// this list is unused and empty, but for each frame we use this /// list to remember which objects we want to render. /// Of course rendering happens only if this list is not empty. /// After each frame this list is cleared again. /// public List lastFrameRenderMatricesAndAlpha = new List(), thisFrameRenderMatricesAndAlpha = new List(); #endregion #region Constructor /// /// Create renderable mesh /// /// Set vertex buffer /// Set index buffer /// Set material /// Set used technique /// Set world parameter /// Set vertex declaration /// Set stream offset /// Set vertex stride /// Set base vertex /// Set number vertices /// Set start index /// Set primitive count public RenderableMesh(VertexBuffer setVertexBuffer, IndexBuffer setIndexBuffer, Material setMaterial, EffectTechnique setUsedTechnique, //EffectParameter setWorldParameter, VertexDeclaration setVertexDeclaration, int setStreamOffset, int setVertexStride, int setBaseVertex, int setNumVertices, int setStartIndex, int setPrimitiveCount) { vertexBuffer = setVertexBuffer; indexBuffer = setIndexBuffer; material = setMaterial; usedTechnique = setUsedTechnique; vertexDeclaration = setVertexDeclaration; streamOffset = setStreamOffset; vertexStride = setVertexStride; baseVertex = setBaseVertex; numVertices = setNumVertices; startIndex = setStartIndex; primitiveCount = setPrimitiveCount; } // RenderableMesh(setVertexBuffer, setIndexBuffer, setMaterial) #endregion #region Render //float lastAlpha = 1.0f; /// /// Render this renderable mesh, MUST be called inside of the /// render method of ShaderEffect.normalMapping! /// /// World matrix public void RenderMesh(Matrix worldMatrix) { // Update world matrix ShaderEffect.normalMapping.WorldMatrix = worldMatrix; ShaderEffect.normalMapping.Effect.CommitChanges(); // Set vertex buffer and index buffer if (lastVertexBufferSet != vertexBuffer || lastIndexBufferSet != indexBuffer) { //tst: BaseGame.Device.VertexDeclaration = vertexDeclaration; lastVertexBufferSet = vertexBuffer; lastIndexBufferSet = indexBuffer; BaseGame.Device.Vertices[0].SetSource( vertexBuffer, streamOffset, vertexStride); BaseGame.Device.Indices = indexBuffer; } // if (vertexBuffer) // And render (this call takes the longest, we can't optimize // it any further because the vertexBuffer and indexBuffer are // WriteOnly, we can't combine it or optimize it any more). BaseGame.Device.DrawIndexedPrimitives( PrimitiveType.TriangleList, baseVertex, 0, numVertices, startIndex, primitiveCount); } // RenderMesh(worldMatrix) /// /// Render /// public void Render() { // Render all meshes we have requested this frame. //lastAlpha = 1.0f; //obs: foreach (Matrix matrix in renderMatrices) for (int matrixNum = 0; matrixNum < lastFrameRenderMatricesAndAlpha.Count; matrixNum++) RenderMesh(lastFrameRenderMatricesAndAlpha[matrixNum]); //*tst // Clear all meshes, don't render them again. // Next frame everything will be created again. lastFrameRenderMatricesAndAlpha.Clear(); //*/ } // Render() #endregion } // class RenderableMesh #endregion #region MeshesPerMaterial helper class /// /// Meshes per material /// public class MeshesPerMaterial { #region Variables /// /// Material /// public Material material; /// /// Meshes /// public List meshes = new List(); #endregion #region Properties /// /// Number of render matrices this material uses this frame. /// /// Int public int NumberOfRenderMatrices { get { int ret = 0; //obs: foreach (RenderableMesh mesh in meshes) for (int meshNum = 0; meshNum < meshes.Count; meshNum++) ret += meshes[meshNum].lastFrameRenderMatricesAndAlpha.Count; return ret; } // get } // NumberOfRenderMatrices #endregion #region Constructor /// /// Create meshes per material for the setMaterial. /// /// Set material public MeshesPerMaterial(Material setMaterial) { material = setMaterial; } // MeshesPerMaterial(setMaterial, setMesh) #endregion #region Add /// /// Adds a renderable mesh using this material. /// /// Add mesh public void Add(RenderableMesh addMesh) { // Make sure this mesh uses the correct material if (addMesh.material != material) throw new Exception("Invalid material, to add a mesh to "+ "MeshesPerMaterial it must use the specified material="+ material); meshes.Add(addMesh); } // Add(addMesh) #endregion #region Render /// /// Render all meshes that use this material. /// This method is only called if we got any meshes to render, /// which is determinated if NumberOfRenderMeshes is greater 0. /// public void Render() { // Set material settings. We don't have to update the shader here, // it will be done in RenderableMesh.Render anyway because of // updating the world matrix! ShaderEffect.normalMapping.SetParametersOptimized(material); // Set vertex declaration BaseGame.Device.VertexDeclaration = meshes[0].vertexDeclaration; // Render all meshes that use this material. //obs: foreach (RenderableMesh mesh in meshes) for (int meshNum = 0; meshNum < meshes.Count; meshNum++) { RenderableMesh mesh = meshes[meshNum]; if (mesh.lastFrameRenderMatricesAndAlpha.Count > 0) mesh.Render(); } // for (meshNum) } // Render() #endregion } // class MeshesPerMaterial #endregion #region MeshesPerMaterialsPerTechniques helper class /// /// Meshes per material per techniques /// public class MeshesPerMaterialPerTechniques { #region Variables /// /// Technique /// public EffectTechnique technique; /// /// Meshes per materials /// public List meshesPerMaterials = new List(); #endregion #region Properties /// /// Number of render matrices this technique uses this frame. /// /// Int public int NumberOfRenderMatrices { get { int ret = 0; //obs: foreach (MeshesPerMaterial list in meshesPerMaterials) for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++) ret += meshesPerMaterials[listNum].NumberOfRenderMatrices; return ret; } // get } // NumberOfRenderMatrices #endregion #region Constructor /// /// Create meshes per material per techniques /// /// Set technique public MeshesPerMaterialPerTechniques(EffectTechnique setTechnique) { technique = setTechnique; } // MeshesPerMaterialPerTechniques(setTechnique) #endregion #region Add /// /// Adds a renderable mesh using this technique. /// /// Add mesh public void Add(RenderableMesh addMesh) { // Make sure this mesh uses the correct material if (addMesh.usedTechnique != technique) throw new Exception("Invalid technique, to add a mesh to "+ "MeshesPerMaterialPerTechniques it must use the specified "+ "technique="+technique.Name); // Search for the used material, maybe we have it already in list. //obs: foreach (MeshesPerMaterial list in meshesPerMaterials) for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++) { MeshesPerMaterial existingList = meshesPerMaterials[listNum]; if (existingList.material == addMesh.material) { // Just add existingList.Add(addMesh); return; } // if (existingList.material) } // for (listNum) // Not found, create new list and add mesh there. MeshesPerMaterial newList = new MeshesPerMaterial(addMesh.material); newList.Add(addMesh); meshesPerMaterials.Add(newList); } // Add(addMesh) #endregion #region Render /// /// Render all meshes that use this technique sorted by the materials. /// This method is only called if we got any meshes to render, /// which is determinated if NumberOfRenderMeshes is greater 0. /// /// Effect public void Render(Effect effect) { // Start effect for this technique //not required, we only got 1 technique: //effect.CurrentTechnique = technique; effect.Begin(SaveStateMode.None); // Render all pass (we always just have one) //obs: foreach (EffectPass pass in effect.CurrentTechnique.Passes) { EffectPass pass = effect.CurrentTechnique.Passes[0]; pass.Begin(); // Render all meshes sorted by all materials. //obs: foreach (MeshesPerMaterial list in meshesPerMaterials) for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++) { MeshesPerMaterial list = meshesPerMaterials[listNum]; if (list.NumberOfRenderMatrices > 0) list.Render(); } // for (listNum) pass.End(); } // foreach (pass) // End shader effect.End(); } // Render(effect) #endregion } // class MeshesPerMaterialPerTechniques #endregion #region Variables /// /// Sorted meshes we got. Everything is sorted by techniques and then /// sorted by materials. This all happens at construction time. /// For rendering use renderMatrices list, which is directly in the /// most inner list of sortedMeshes (the RenderableMesh objects). /// List sortedMeshes = new List(); #endregion #region Add /// /// Add model mesh part with the used effect to our sortedMeshes list. /// Neither the model mesh part nor the effect is directly used, /// we will extract all data from the model and only render the /// index and vertex buffers later. /// The model mesh part must use the TangentVertex format. /// /// Vertex buffer /// Index buffer /// Part /// Effect /// Renderable mesh public RenderableMesh Add(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, ModelMeshPart part, Effect effect) { string techniqueName = effect.CurrentTechnique.Name; // Does this technique already exists? MeshesPerMaterialPerTechniques foundList = null; //obs: foreach (MeshesPerMaterialPerTechniques list in sortedMeshes) for (int listNum = 0; listNum < sortedMeshes.Count; listNum++) { MeshesPerMaterialPerTechniques list = sortedMeshes[listNum]; if (list.technique != null && list.technique.Name == techniqueName) { foundList = list; break; } // if (list.technique.Name) } // for (listNum) // Did not found list? Create new one if (foundList == null) { EffectTechnique technique = ShaderEffect.normalMapping.GetTechnique(techniqueName); // Make sure we always have a valid technique if (technique == null) { if (BaseGame.CanUsePS20) techniqueName = "Diffuse20";//"Specular20"; else techniqueName = "Diffuse";//"Specular"; technique = ShaderEffect.normalMapping.GetTechnique(techniqueName); } // if foundList = new MeshesPerMaterialPerTechniques(technique); sortedMeshes.Add(foundList); } // if (foundList) // Create new material from the current effect parameters. // This will create duplicate materials if the same material is used // multiple times, we check this later. Material material = new Material(effect); // Search for material inside foundList. //obs: foreach (MeshesPerMaterial innerList in foundList.meshesPerMaterials) for (int innerListNum = 0; innerListNum < foundList.meshesPerMaterials.Count; innerListNum++) { MeshesPerMaterial innerList = foundList.meshesPerMaterials[innerListNum]; // Check if this is the same material and we can use it instead. // For our purposes it is sufficiant if we check textures and colors. if (innerList.material.diffuseTexture == material.diffuseTexture && innerList.material.normalTexture == material.normalTexture && innerList.material.ambientColor == material.ambientColor && innerList.material.diffuseColor == material.diffuseColor && innerList.material.specularColor == material.specularColor && innerList.material.specularPower == material.specularPower) { // Reuse this material and quit this search material = innerList.material; break; } // if (innerList.material.diffuseTexture) } // foreach (innerList) // Build new RenderableMesh object RenderableMesh mesh = new RenderableMesh( vertexBuffer, indexBuffer, material, foundList.technique, part.VertexDeclaration, part.StreamOffset, part.VertexStride, part.BaseVertex, part.NumVertices, part.StartIndex, part.PrimitiveCount); foundList.Add(mesh); return mesh; } // Add(vertexBuffer, indexBuffer, part) #endregion #region Render /// /// Render all meshes we collected this frame sorted by techniques /// and materials. This method is about 3-5 times faster than just /// using Model's Mesh.Draw method (see commented out code there). /// The reason for that is that we require only very few state changes /// and render everthing down as fast as we can. The only optimization /// left would be to put vertices of several meshes together if they /// are static and use the same technique and material. But since /// meshes have WriteOnly vertex and index buffers, we can't do that /// without using a custom model format. /// public void Render() { // Copy over last frame's object to this frame for (int listNum = 0; listNum < sortedMeshes.Count; listNum++) { MeshesPerMaterialPerTechniques list = sortedMeshes[listNum]; for (int meshNum = 0; meshNum < list.meshesPerMaterials.Count; meshNum++) { MeshesPerMaterial list2 = list.meshesPerMaterials[meshNum]; for (int num = 0; num < list2.meshes.Count; num++) { RenderableMesh mesh = list2.meshes[num]; // Copy over last frame matrices for rendering now! mesh.lastFrameRenderMatricesAndAlpha = mesh.thisFrameRenderMatricesAndAlpha; // Clear list for this frame mesh.thisFrameRenderMatricesAndAlpha = new List(); } // for } // for } // for // Make sure z buffer is on for 3D content BaseGame.Device.RenderState.DepthBufferEnable = true; BaseGame.Device.RenderState.DepthBufferWriteEnable = true; // We always use the normalMapping shader here. Effect effect = ShaderEffect.normalMapping.Effect; // Set general parameters for the shader ShaderEffect.normalMapping.SetParametersOptimizedGeneral(); // Don't set vertex buffer again if it does not change this frame. // Clear these remember settings. lastVertexBufferSet = null; lastIndexBufferSet = null; //obs: foreach (MeshesPerMaterialPerTechniques list in sortedMeshes) for (int listNum = 0; listNum < sortedMeshes.Count; listNum++) { MeshesPerMaterialPerTechniques list = sortedMeshes[listNum]; if (list.NumberOfRenderMatrices > 0) list.Render(effect); } // for (listNum) } // Render() #endregion #region ClearAll /// /// Clear all objects in the render list in case our device got lost. /// public void ClearAll() { // Copy over last frame's object to this frame for (int listNum = 0; listNum < sortedMeshes.Count; listNum++) { MeshesPerMaterialPerTechniques list = sortedMeshes[listNum]; for (int meshNum = 0; meshNum < list.meshesPerMaterials.Count; meshNum++) { MeshesPerMaterial list2 = list.meshesPerMaterials[meshNum]; for (int num = 0; num < list2.meshes.Count; num++) { RenderableMesh mesh = list2.meshes[num]; // Clear all mesh.lastFrameRenderMatricesAndAlpha.Clear(); mesh.thisFrameRenderMatricesAndAlpha.Clear(); } // for } // for } // for } // ClearAll() #endregion } // class MeshRenderManager } // namespace XnaShooter.Graphics