// 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