MeshRenderManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. //-----------------------------------------------------------------------------
  2. // MeshRenderManager.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using Microsoft.Xna.Framework;
  8. using Microsoft.Xna.Framework.Graphics;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Text;
  12. using RacingGame.GameLogic;
  13. using RacingGame.Helpers;
  14. using RacingGame.Shaders;
  15. using RacingGame.Tracks;
  16. namespace RacingGame.Graphics
  17. {
  18. /// <summary>
  19. /// Mesh render manager, a little helper class which allows us to render
  20. /// all our models with much faster performance through sorting by
  21. /// material and shader techniques.
  22. ///
  23. /// We keep a list of lists of lists for rendering our RenderableMeshes
  24. /// sorted by techniques first and materials second.
  25. /// The most outer list contains all techniques (see
  26. /// MeshesPerMaterialPerTechniques).
  27. /// Then the first inner list contains all materials (see MeshesPerMaterial).
  28. /// And finally the most inner list contains all meshes that use the
  29. /// technique and material we got.
  30. /// Additionally we could also sort by shaders, but since all models
  31. /// use the same shader (normalMapping), that is the only thing we support
  32. /// here. Improving it to support more shaders is easily possible.
  33. ///
  34. /// All this is created in the Model constructor. At runtime we just
  35. /// go through these lists and render everything down as quickly as
  36. /// possible.
  37. /// </summary>
  38. public class MeshRenderManager
  39. {
  40. /// <summary>
  41. /// Don't set vertex and index buffers again if they are already set this
  42. /// frame.
  43. /// </summary>
  44. static VertexBuffer lastVertexBufferSet = null;
  45. static IndexBuffer lastIndexBufferSet = null;
  46. /// <summary>
  47. /// Renderable mesh, created in the Model constructor and is rendered
  48. /// when we render all the models at once at the end of each frame!
  49. /// </summary>
  50. public class RenderableMesh
  51. {
  52. /// <summary>
  53. /// Vertex buffer
  54. /// </summary>
  55. public VertexBuffer vertexBuffer;
  56. /// <summary>
  57. /// Index buffer
  58. /// </summary>
  59. public IndexBuffer indexBuffer;
  60. /// <summary>
  61. /// Material
  62. /// </summary>
  63. public Material material;
  64. /// <summary>
  65. /// Used technique
  66. /// </summary>
  67. public EffectTechnique usedTechnique;
  68. /// <summary>
  69. /// World parameter
  70. /// </summary>
  71. public EffectParameter worldParameter;
  72. /// <summary>
  73. /// Stream offset, vertex stride, etc.
  74. /// All parameters we need for rendering.
  75. /// </summary>
  76. public int baseVertex, numVertices, startIndex, primitiveCount;
  77. /// <summary>
  78. /// List of render matrices we use every frame. At creation time
  79. /// this list is unused and empty, but for each frame we use this
  80. /// list to remember which objects we want to render.
  81. /// Of course rendering happens only if this list is not empty.
  82. /// After each frame this list is cleared again.
  83. /// </summary>
  84. public List<Matrix> renderMatrices = new List<Matrix>();
  85. /// <summary>
  86. /// Create renderable mesh
  87. /// </summary>
  88. /// <param name="setVertexBuffer">Set vertex buffer</param>
  89. /// <param name="setIndexBuffer">Set index buffer</param>
  90. /// <param name="setMaterial">Set material</param>
  91. /// <param name="setUsedTechnique">Set used technique</param>
  92. /// <param name="setWorldParameter">Set world parameter</param>
  93. /// <param name="setBaseVertex">Set base vertex</param>
  94. /// <param name="setNumVertices">Set number vertices</param>
  95. /// <param name="setStartIndex">Set start index</param>
  96. /// <param name="setPrimitiveCount">Set primitive count</param>
  97. public RenderableMesh(VertexBuffer setVertexBuffer,
  98. IndexBuffer setIndexBuffer, Material setMaterial,
  99. EffectTechnique setUsedTechnique, EffectParameter setWorldParameter,
  100. int setBaseVertex,
  101. int setNumVertices, int setStartIndex, int setPrimitiveCount)
  102. {
  103. vertexBuffer = setVertexBuffer;
  104. indexBuffer = setIndexBuffer;
  105. material = setMaterial;
  106. usedTechnique = setUsedTechnique;
  107. worldParameter = setWorldParameter;
  108. baseVertex = setBaseVertex;
  109. numVertices = setNumVertices;
  110. startIndex = setStartIndex;
  111. primitiveCount = setPrimitiveCount;
  112. }
  113. /// <summary>
  114. /// Render this renderable mesh, MUST be called inside of the
  115. /// render method of ShaderEffect.normalMapping!
  116. /// </summary>
  117. /// <param name="worldMatrix">World matrix</param>
  118. public void RenderMesh(Matrix worldMatrix)
  119. {
  120. // Update world matrix
  121. ShaderEffect.normalMapping.WorldMatrix = worldMatrix;
  122. ShaderEffect.normalMapping.Effect.CurrentTechnique.Passes[0].Apply();//.Update();
  123. // Set vertex buffer and index buffer
  124. if (lastVertexBufferSet != vertexBuffer ||
  125. lastIndexBufferSet != indexBuffer)
  126. {
  127. lastVertexBufferSet = vertexBuffer;
  128. lastIndexBufferSet = indexBuffer;
  129. BaseGame.Device.SetVertexBuffer(vertexBuffer);
  130. BaseGame.Device.Indices = indexBuffer;
  131. }
  132. // And render (this call takes the longest, we can't optimize
  133. // it any further because the vertexBuffer and indexBuffer are
  134. // WriteOnly, we can't combine it or optimize it any more).
  135. BaseGame.Device.DrawIndexedPrimitives(
  136. PrimitiveType.TriangleList,
  137. baseVertex, startIndex, primitiveCount);
  138. }
  139. /// <summary>
  140. /// Render
  141. /// </summary>
  142. public void Render()
  143. {
  144. // Render all meshes we have requested this frame.
  145. for (int matrixNum = 0; matrixNum < renderMatrices.Count; matrixNum++)
  146. RenderMesh(renderMatrices[matrixNum]);
  147. // Clear all meshes, don't render them again.
  148. // Next frame everything will be created again.
  149. renderMatrices.Clear();
  150. }
  151. }
  152. /// <summary>
  153. /// Meshes per material
  154. /// </summary>
  155. public class MeshesPerMaterial
  156. {
  157. /// <summary>
  158. /// Material
  159. /// </summary>
  160. public Material material;
  161. /// <summary>
  162. /// Meshes
  163. /// </summary>
  164. public List<RenderableMesh> meshes = new List<RenderableMesh>();
  165. /// <summary>
  166. /// Number of render matrices this material uses this frame.
  167. /// </summary>
  168. /// <returns>Int</returns>
  169. public int NumberOfRenderMatrices
  170. {
  171. get
  172. {
  173. int ret = 0;
  174. for (int meshNum = 0; meshNum < meshes.Count; meshNum++)
  175. ret += meshes[meshNum].renderMatrices.Count;
  176. return ret;
  177. }
  178. }
  179. /// <summary>
  180. /// Create meshes per material for the setMaterial.
  181. /// </summary>
  182. /// <param name="setMaterial">Set material</param>
  183. public MeshesPerMaterial(Material setMaterial)
  184. {
  185. material = setMaterial;
  186. }
  187. /// <summary>
  188. /// Adds a renderable mesh using this material.
  189. /// </summary>
  190. /// <param name="addMesh">Add mesh</param>
  191. public void Add(RenderableMesh addMesh)
  192. {
  193. // Make sure this mesh uses the correct material
  194. if (addMesh.material != material)
  195. throw new ArgumentException("Invalid material, to add a mesh to " +
  196. "MeshesPerMaterial it must use the specified material=" +
  197. material);
  198. meshes.Add(addMesh);
  199. }
  200. /// <summary>
  201. /// Render all meshes that use this material.
  202. /// This method is only called if we got any meshes to render,
  203. /// which is determinated if NumberOfRenderMeshes is greater 0.
  204. /// </summary>
  205. public void Render()
  206. {
  207. // Set material settings. We don't have to update the shader here,
  208. // it will be done in RenderableMesh.Render anyway because of
  209. // updating the world matrix!
  210. ShaderEffect.normalMapping.SetParametersOptimized(material);
  211. // Set vertex declaration
  212. //always true: if (meshes.Count > 0)
  213. // Enable alpha if this material uses alpha
  214. if (material.HasAlpha)
  215. {
  216. //TODO: AlphaTestEffect
  217. //BaseGame.Device.RenderState.AlphaTestEnable = true;
  218. //BaseGame.Device.RenderState.ReferenceAlpha = 128;
  219. // Make 2sided, we use alpha mainly for our palms.
  220. BaseGame.Device.RasterizerState = RasterizerState.CullNone;
  221. }
  222. // Render all meshes that use this material.
  223. for (int meshNum = 0; meshNum < meshes.Count; meshNum++)
  224. {
  225. RenderableMesh mesh = meshes[meshNum];
  226. if (mesh.renderMatrices.Count > 0)
  227. mesh.Render();
  228. }
  229. // Disable alpha testing again and restore culling
  230. if (material.HasAlpha)
  231. {
  232. //TODO: AlphaTestEffect
  233. //BaseGame.Device.RenderState.AlphaTestEnable = false;
  234. BaseGame.Device.RasterizerState = RasterizerState.CullCounterClockwise;
  235. }
  236. }
  237. }
  238. /// <summary>
  239. /// Meshes per material per techniques
  240. /// </summary>
  241. public class MeshesPerMaterialPerTechniques
  242. {
  243. /// <summary>
  244. /// Technique
  245. /// </summary>
  246. public EffectTechnique technique;
  247. /// <summary>
  248. /// Meshes per materials
  249. /// </summary>
  250. public List<MeshesPerMaterial> meshesPerMaterials =
  251. new List<MeshesPerMaterial>();
  252. /// <summary>
  253. /// Number of render matrices this technique uses this frame.
  254. /// </summary>
  255. /// <returns>Int</returns>
  256. public int NumberOfRenderMatrices
  257. {
  258. get
  259. {
  260. int ret = 0;
  261. try
  262. {
  263. for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++)
  264. ret += meshesPerMaterials[listNum].NumberOfRenderMatrices;
  265. }
  266. catch
  267. {
  268. }
  269. return ret;
  270. }
  271. }
  272. /// <summary>
  273. /// Create meshes per material per techniques
  274. /// </summary>
  275. /// <param name="setTechnique">Set technique</param>
  276. public MeshesPerMaterialPerTechniques(EffectTechnique setTechnique)
  277. {
  278. technique = setTechnique;
  279. }
  280. /// <summary>
  281. /// Adds a renderable mesh using this technique.
  282. /// </summary>
  283. /// <param name="addMesh">Add mesh</param>
  284. public void Add(RenderableMesh addMesh)
  285. {
  286. // Make sure this mesh uses the correct material
  287. if (addMesh.usedTechnique != technique)
  288. throw new ArgumentException("Invalid technique, to add a mesh to " +
  289. "MeshesPerMaterialPerTechniques it must use the specified " +
  290. "technique=" + technique.Name);
  291. // Search for the used material, maybe we have it already in list.
  292. for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++)
  293. {
  294. MeshesPerMaterial existingList = meshesPerMaterials[listNum];
  295. if (existingList.material == addMesh.material)
  296. {
  297. // Just add
  298. existingList.Add(addMesh);
  299. return;
  300. }
  301. }
  302. // Not found, create new list and add mesh there.
  303. MeshesPerMaterial newList = new MeshesPerMaterial(addMesh.material);
  304. newList.Add(addMesh);
  305. meshesPerMaterials.Add(newList);
  306. }
  307. /// <summary>
  308. /// Render all meshes that use this technique sorted by the materials.
  309. /// This method is only called if we got any meshes to render,
  310. /// which is determinated if NumberOfRenderMeshes is greater 0.
  311. /// </summary>
  312. /// <param name="effect">Effect</param>
  313. public void Render(Effect effect)
  314. {
  315. // Start effect for this technique
  316. effect.CurrentTechnique = technique;
  317. // Render all pass (we always just have one)
  318. EffectPass pass = effect.CurrentTechnique.Passes[0];
  319. pass.Apply();
  320. // Render all meshes sorted by all materials.
  321. for (int listNum = 0; listNum < meshesPerMaterials.Count; listNum++)
  322. {
  323. MeshesPerMaterial list = meshesPerMaterials[listNum];
  324. if (list.NumberOfRenderMatrices > 0)
  325. list.Render();
  326. }
  327. }
  328. }
  329. /// <summary>
  330. /// Sorted meshes we got. Everything is sorted by techniques and then
  331. /// sorted by materials. This all happens at construction time.
  332. /// For rendering use renderMatrices list, which is directly in the
  333. /// most inner list of sortedMeshes (the RenderableMesh objects).
  334. /// </summary>
  335. List<MeshesPerMaterialPerTechniques> sortedMeshes =
  336. new List<MeshesPerMaterialPerTechniques>();
  337. /// <summary>
  338. /// Add model mesh part with the used effect to our sortedMeshes list.
  339. /// Neither the model mesh part nor the effect is directly used,
  340. /// we will extract all data from the model and only render the
  341. /// index and vertex buffers later.
  342. /// The model mesh part must use the TangentVertex format.
  343. /// </summary>
  344. /// <param name="vertexBuffer">Vertex buffer</param>
  345. /// <param name="indexBuffer">Index buffer</param>
  346. /// <param name="part">Part</param>
  347. /// <param name="effect">Effect</param>
  348. /// <returns>Renderable mesh</returns>
  349. public RenderableMesh Add(VertexBuffer vertexBuffer,
  350. IndexBuffer indexBuffer, ModelMeshPart part, Effect effect)
  351. {
  352. string techniqueName = effect.CurrentTechnique.Name;
  353. // Does this technique already exist?
  354. MeshesPerMaterialPerTechniques foundList = null;
  355. for (int listNum = 0; listNum < sortedMeshes.Count; listNum++)
  356. {
  357. MeshesPerMaterialPerTechniques list = sortedMeshes[listNum];
  358. if (list.technique.Name == techniqueName)
  359. {
  360. foundList = list;
  361. break;
  362. }
  363. }
  364. // Did not found list? Create new one
  365. if (foundList == null)
  366. {
  367. EffectTechnique technique = ShaderEffect.normalMapping.GetTechnique(techniqueName);
  368. if (technique == null)
  369. {
  370. // techniqueName came from a BasicEffect (MonoGame XImporter doesn't preserve
  371. // EffectMaterialContent from .x files), so fall back to a valid NormalMapping technique.
  372. technique = ShaderEffect.normalMapping.GetTechnique("DiffuseSpecular20");
  373. }
  374. foundList = new MeshesPerMaterialPerTechniques(technique);
  375. sortedMeshes.Add(foundList);
  376. }
  377. // Create new material from the current effect parameters.
  378. // This will create duplicate materials if the same material is used
  379. // multiple times, we check this later.
  380. Material material = new Material(effect);
  381. // If the effect is a BasicEffect (MonoGame XImporter fallback), extract
  382. // the texture from it since Material(Effect) only reads NormalMapping params.
  383. if (material.diffuseTexture == null && effect is BasicEffect basicEffect &&
  384. basicEffect.Texture != null)
  385. {
  386. material.diffuseTexture = new Texture(basicEffect.Texture);
  387. }
  388. // Search for material inside foundList.
  389. for (int innerListNum = 0; innerListNum <
  390. foundList.meshesPerMaterials.Count; innerListNum++)
  391. {
  392. MeshesPerMaterial innerList =
  393. foundList.meshesPerMaterials[innerListNum];
  394. // Check if this is the same material and we can use it instead.
  395. // For our purposes it is sufficiant if we check textures and colors.
  396. if (innerList.material.diffuseTexture == material.diffuseTexture &&
  397. innerList.material.normalTexture == material.normalTexture &&
  398. innerList.material.ambientColor == material.ambientColor &&
  399. innerList.material.diffuseColor == material.diffuseColor &&
  400. innerList.material.specularColor == material.specularColor &&
  401. innerList.material.specularPower == material.specularPower)
  402. {
  403. // Reuse this material and quit this search
  404. material = innerList.material;
  405. break;
  406. }
  407. }
  408. // Build new RenderableMesh object
  409. RenderableMesh mesh = new RenderableMesh(
  410. vertexBuffer, indexBuffer, material, foundList.technique,
  411. ShaderEffect.normalMapping.WorldParameter, part.VertexOffset,
  412. part.NumVertices, part.StartIndex, part.PrimitiveCount);
  413. foundList.Add(mesh);
  414. return mesh;
  415. }
  416. /// <summary>
  417. /// Render all meshes we collected this frame sorted by techniques
  418. /// and materials. This method is about 3-5 times faster than just
  419. /// using Model's Mesh.Draw method (see commented out code there).
  420. /// The reason for that is that we require only very few state changes
  421. /// and render everthing down as fast as we can. The only optimization
  422. /// left would be to put vertices of several meshes together if they
  423. /// are static and use the same technique and material. But since
  424. /// meshes have WriteOnly vertex and index buffers, we can't do that
  425. /// without using a custom model format.
  426. /// </summary>
  427. public void Render()
  428. {
  429. // Make sure z buffer is on
  430. BaseGame.Device.DepthStencilState = DepthStencilState.Default;
  431. // We always use the normalMapping shader here.
  432. Effect effect = ShaderEffect.normalMapping.Effect;
  433. // Set general parameters for the shader
  434. ShaderEffect.normalMapping.SetParametersOptimizedGeneral();
  435. // Don't set vertex buffer again if it does not change this frame.
  436. // Clear these remember settings.
  437. lastVertexBufferSet = null;
  438. lastIndexBufferSet = null;
  439. for (int listNum = 0; listNum < sortedMeshes.Count; listNum++)
  440. {
  441. MeshesPerMaterialPerTechniques list = sortedMeshes[listNum];
  442. if (list != null && list.NumberOfRenderMatrices > 0)
  443. list.Render(effect);
  444. }
  445. }
  446. }
  447. }