PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Graphics/OpenTK/OpenTKShader.cs

#
C# | 506 lines | 313 code | 46 blank | 147 comment | 52 complexity | 2513775459881b3f836558f8e50bd3da MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using Delta.ContentSystem.Rendering.Helpers;
  3. using Delta.Engine;
  4. using Delta.Engine.Dynamic;
  5. using Delta.Graphics.BaseOpenGL;
  6. using Delta.Utilities;
  7. using Delta.Utilities.Datatypes;
  8. using Delta.Utilities.Graphics.ShaderFeatures;
  9. using Delta.Utilities.Helpers;
  10. using OpenTK.Graphics.OpenGL;
  11. namespace Delta.Graphics.OpenTK
  12. {
  13. /// <summary>
  14. /// OpenTK shader implementation.
  15. /// </summary>
  16. internal class OpenTKShader : BaseOpenGLShader
  17. {
  18. #region Private
  19. #region lastActiveTextureUnit (Private)
  20. /// <summary>
  21. /// Simple cache to prevent setting the same active texture unit over
  22. /// and over again. Many times we only need the first unit anyways.
  23. /// </summary>
  24. private static TextureUnit lastActiveTextureUnit = TextureUnit.Texture0;
  25. #endregion
  26. #endregion
  27. #region Constructors
  28. /// <summary>
  29. /// Creates a OpenTK shader with a given shader content name
  30. /// </summary>
  31. /// <param name="setShaderName">Shader content name</param>
  32. public OpenTKShader(string setShaderName)
  33. : base(setShaderName)
  34. {
  35. }
  36. /// <summary>
  37. /// Create a OpenTK shader with a given shader flags
  38. /// </summary>
  39. /// <param name="setShaderFlags">Shader flags to search for</param>
  40. public OpenTKShader(ShaderFeatureFlags setShaderFlags)
  41. : base(setShaderFlags)
  42. {
  43. }
  44. #endregion
  45. #region Render (Public)
  46. /// <summary>
  47. /// Draw something in 2D or 3D with this shader, should be called as few
  48. /// times as possible (rendering is much faster without many shader
  49. /// switches). This is currently ONLY used from the MaterialManager!
  50. /// Derived implementations MUST call base.Render(), and should only
  51. /// continue rendering if this method returns true
  52. /// </summary>
  53. /// <param name="renderDelegate">This is the delegate we pass from the
  54. /// MaterialManager, which will render all RenderGeometries with the
  55. /// pre-calculated geometries and their materials (we don't know anything
  56. /// about that here, it is all handled and sorted over there).</param>
  57. /// <returns></returns>
  58. public override bool Render(RunDelegate renderDelegate)
  59. {
  60. if (Graphic.Instance.HasShaderSupport)
  61. {
  62. if (ShaderHandle == MathHelper.InvalidIndex)
  63. {
  64. Log.Warning("Cannot render with uninitialized shader: " + this);
  65. return false;
  66. }
  67. // Use this shader program
  68. UseProgram();
  69. }
  70. else
  71. {
  72. if (data.UsesTexturing)
  73. {
  74. GL.Enable(EnableCap.Texture2D);
  75. }
  76. else
  77. {
  78. GL.Disable(EnableCap.Texture2D);
  79. }
  80. }
  81. // And render all geometry as setup in Delta.Rendering.MaterialManager
  82. renderDelegate();
  83. if (Graphic.Instance.HasShaderSupport == false)
  84. {
  85. GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
  86. GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
  87. OpenTKGraphic.DisableFFPVertexFormat(VertexFormat);
  88. // If we used something with color, we need to reset the color to white
  89. if (data.UsesVertexColoring)
  90. {
  91. GL.Color4(1.0f, 1.0f, 1.0f, 1.0f);
  92. }
  93. }
  94. return true;
  95. }
  96. #endregion
  97. #region Methods (Private)
  98. #region CompileShader
  99. /// <summary>
  100. /// Create a new shader, set the source and compile it.
  101. /// </summary>
  102. protected override bool CompileShader(bool isVertexShader,
  103. string shaderCode, ref int shader)
  104. {
  105. // We need to make sure that our graphic device is set, else this
  106. // whole class makes no sense!
  107. if (BaseOpenGLGraphic.IsValidAndCurrent == false)
  108. {
  109. throw new NotSupportedException(
  110. "OpenTKShader(" + data.Name +
  111. ") cannot be used when OpenTKGraphic has not been initialized!\n" +
  112. "There must be something wrong with creating the graphic device: " +
  113. Settings.Modules.GraphicModule +
  114. // Extra help text if the graphics module is not OpenTK!
  115. (Settings.Modules.GraphicModule.Contains("OpenTK") == false
  116. ? "\nYou problem is that you wanted to create " +
  117. Settings.Modules.GraphicModule +
  118. " graphics, but that failed or " +
  119. "was not found and you ended up with OpenTKShader, which needs " +
  120. "the OpenTKGraphic device to be up!"
  121. : ""));
  122. }
  123. shader = GL.CreateShader(
  124. isVertexShader
  125. ? ShaderType.VertexShader
  126. : ShaderType.FragmentShader);
  127. int length = shaderCode.Length;
  128. GL.ShaderSource(shader, shaderCode);
  129. GL.CompileShader(shader);
  130. int state = 0;
  131. GL.GetShader(shader, ShaderParameter.CompileStatus, out state);
  132. return state != 0;
  133. }
  134. #endregion
  135. #region GetShaderErrorLog
  136. /// <summary>
  137. /// Get shader info log, will not only get the error info
  138. /// log message, but also kill the shader itself.
  139. /// </summary>
  140. protected override string GetShaderErrorLog(int shader, bool killShader)
  141. {
  142. string errorLog = GL.GetShaderInfoLog(shader);
  143. // Delete the broken shader if this was a fatal error
  144. if (killShader)
  145. {
  146. GL.DeleteShader(shader);
  147. shader = MathHelper.InvalidIndex;
  148. }
  149. return errorLog;
  150. }
  151. #endregion
  152. #region CreateProgram
  153. /// <summary>
  154. /// Create program
  155. /// </summary>
  156. protected override int CreateProgram(int vertexShader,
  157. int fragmentShader)
  158. {
  159. int program = GL.CreateProgram();
  160. GL.AttachShader(program, vertexShader);
  161. GL.AttachShader(program, fragmentShader);
  162. return program;
  163. }
  164. #endregion
  165. #region LinkProgram
  166. /// <summary>
  167. /// Link program
  168. /// </summary>
  169. protected override bool LinkProgram()
  170. {
  171. if (Graphic.Instance.HasShaderSupport)
  172. {
  173. GL.LinkProgram(shaderHandle);
  174. int linkStatus;
  175. GL.GetProgram(shaderHandle, ProgramParameter.LinkStatus, out linkStatus);
  176. return linkStatus != 0;
  177. }
  178. return true;
  179. }
  180. #endregion
  181. #region UseProgram
  182. /// <summary>
  183. /// Use program
  184. /// </summary>
  185. protected override void UseProgram()
  186. {
  187. // Note: We need to use the ShaderHandle property here to force loading
  188. // the shader if we have not done yet. All calls after this will assume
  189. // that the shader has been loaded (all properties, draw calls, etc.)
  190. GL.UseProgram(ShaderHandle);
  191. }
  192. #endregion
  193. #region GetProgramErrorLog
  194. /// <summary>
  195. /// Get program error log
  196. /// </summary>
  197. protected override string GetProgramErrorLog()
  198. {
  199. int length;
  200. GL.GetProgram(shaderHandle, ProgramParameter.InfoLogLength, out length);
  201. string infoLog = GL.GetProgramInfoLog(shaderHandle);
  202. ErrorCode error = GL.GetError();
  203. if (error != ErrorCode.NoError)
  204. {
  205. Log.Warning("There was an OpenGL error while creating the shader " +
  206. "'" + this + "': " + error);
  207. }
  208. return infoLog;
  209. }
  210. #endregion
  211. #region DeleteProgram
  212. /// <summary>
  213. /// Delete the shader program.
  214. /// </summary>
  215. protected override void DeleteProgram()
  216. {
  217. GL.DeleteProgram(shaderHandle);
  218. }
  219. #endregion
  220. #region GetAttribLocation
  221. /// <summary>
  222. /// GetAttribLocation implementation.
  223. /// </summary>
  224. protected override int GetAttribLocation(string name)
  225. {
  226. return GL.GetAttribLocation(shaderHandle, name);
  227. }
  228. #endregion
  229. #region GetUniformHandle
  230. /// <summary>
  231. /// Get uniform location
  232. /// </summary>
  233. protected override object GetUniformHandle(ShaderUniformNames name)
  234. {
  235. int uniformId = GL.GetUniformLocation(shaderHandle, name.ToString());
  236. // Note: If uniform was not found, do not return -1, return null instead.
  237. // This way the base Shader class can optimize all properties easily
  238. // with a unequal to null check (can't be an int because of other
  239. // graphic platforms that do not have integers for shader parameters).
  240. return uniformId != MathHelper.InvalidIndex
  241. ? (object)uniformId
  242. : null;
  243. }
  244. #endregion
  245. #region SetUniform
  246. /// <summary>
  247. /// Set uniform float (for some special settings, e.g. fog, time, etc.).
  248. /// Note: Only called if the given uniform handle id is valid.
  249. /// </summary>
  250. protected override void SetUniform(object uniformId, float value)
  251. {
  252. GL.Uniform1((int)uniformId, value);
  253. }
  254. /// <summary>
  255. /// Set uniform Vector (for some advanced shaders)
  256. /// Note: Only called if the given uniform handle id is valid.
  257. /// </summary>
  258. protected override void SetUniform(object uniformId, Vector value)
  259. {
  260. GL.Uniform3((int)uniformId, value.X, value.Y, value.Z);
  261. }
  262. /// <summary>
  263. /// Set uniform Matrix (for world, viewInverse and worldViewProj).
  264. /// A boxed reference value is used here to increase performance.
  265. /// Note: Only called if the given uniform handle id is valid.
  266. /// </summary>
  267. protected override void SetUniform(object uniformId, ref Matrix value)
  268. {
  269. GL.UniformMatrix4((int)uniformId, 1, false, ref value.M11);
  270. }
  271. /// <summary>
  272. /// Set uniform Matrix array (for skinning)
  273. /// Note: Only called if the given uniform handle id is valid.
  274. /// </summary>
  275. protected override void SetUniform(object uniformId, Matrix[] value)
  276. {
  277. // Set all matrices at once :)
  278. GL.UniformMatrix4((int)uniformId, value.Length, false, ref value[0].M11);
  279. }
  280. /// <summary>
  281. /// Set uniform Color (for material colors)
  282. /// Note: Only called if the given uniform handle id is valid.
  283. /// </summary>
  284. protected override void SetUniform(object uniformId, Color value)
  285. {
  286. // We can just use the uint version of Uniform1 to set all 4 bytes!
  287. //no we can't, it crashes: System.EntryPointNotFoundException: Unable to find an entry point named 'glUniform1ui' in DLL 'opengl32.dll'.: GL.Uniform1((int)uniformId, value.PackedRGBA);
  288. // Using the int version instead.
  289. //crashes with InvalidOperation exception, which is even worse (happens only in debug, in release mode we get some random gl error much later):
  290. // GL.Uniform1((int)uniformId, (int)value.PackedRGBA);
  291. //crashes too with InvalidOperation because vec4 is not 4 bytes:
  292. //.RedByte, value.GreenByte, value.BlueByte, value.AlphaByte);
  293. //good help: http://www.opengl.org/sdk/docs/man/xhtml/glUniform.xml
  294. // This works, might not be the most efficient, but it works :)
  295. GL.Uniform4((int)uniformId, value.R, value.G, value.B, value.A);
  296. }
  297. /// <summary>
  298. /// Set uniform Point (rarely used (PostScreenWindowSize for example))
  299. /// Note: Only called if the given uniform handle id is valid.
  300. /// </summary>
  301. protected override void SetUniform(object uniformId, Point value)
  302. {
  303. GL.Uniform2((int)uniformId, value.X, value.Y);
  304. }
  305. /// <summary>
  306. /// Set uniform
  307. /// </summary>
  308. protected override void SetUniform(object uniformHandle,
  309. BaseTexture value)
  310. {
  311. if (uniformHandle != null &&
  312. value != null)
  313. {
  314. int imageHande = 0;
  315. BaseOpenGLTexture glImage = value as BaseOpenGLTexture;
  316. if (glImage != null)
  317. {
  318. imageHande = glImage.TextureHandle;
  319. }
  320. else
  321. {
  322. BaseOpenGLRenderToTexture glTexture =
  323. value as BaseOpenGLRenderToTexture;
  324. if (glTexture != null)
  325. {
  326. imageHande = glTexture.TextureHandle;
  327. }
  328. else
  329. {
  330. Log.Warning("The given value: '" + value + "' is not supported " +
  331. "in OpenGL.");
  332. }
  333. }
  334. TextureUnit unit = TextureUnit.Texture0;
  335. if (uniformHandle == normalMapHandle)
  336. {
  337. unit = TextureUnit.Texture1;
  338. }
  339. // We currently only support either specular maps or detail maps.
  340. else if (uniformHandle == specularMapHandle)
  341. {
  342. unit = TextureUnit.Texture2;
  343. }
  344. else if (uniformHandle == shaderLutTextureHandle)
  345. {
  346. // If we have a specular map already, use the next texture.
  347. // Note: Lightmaps will not work this way (overrides texture)!
  348. if (specularMapHandle != null)
  349. {
  350. unit = TextureUnit.Texture3;
  351. }
  352. else
  353. {
  354. unit = TextureUnit.Texture2;
  355. }
  356. }
  357. else if (uniformHandle == heightMapHandle)
  358. {
  359. // Height map is only allowed as texture unit 2 (no specular)
  360. unit = TextureUnit.Texture2;
  361. }
  362. // LightMaps are currently always in the last texture unit!
  363. else if (uniformHandle == lightMapHandle)
  364. {
  365. unit = TextureUnit.Texture3;
  366. }
  367. else if (uniformHandle == shadowMapTextureHandle)
  368. {
  369. //0, 1, 2, 3 can all be used, e.g. NormalMapParallaxLightMap,
  370. // but we still want shadow for that too.
  371. unit = TextureUnit.Texture4;
  372. }
  373. else if (uniformHandle == detailMapHandle)
  374. {
  375. // Use unit 5 as our texture
  376. unit = TextureUnit.Texture5;
  377. }
  378. // Note: We only need to change the active texture channel if it
  379. // changes. This is usually the case when setting complex shaders,
  380. // but many times we just need the first texture channel.
  381. if (unit != lastActiveTextureUnit)
  382. {
  383. lastActiveTextureUnit = unit;
  384. GL.ActiveTexture(unit);
  385. }
  386. GL.BindTexture(TextureTarget.Texture2D, imageHande);
  387. GL.Uniform1((int)uniformHandle, unit - TextureUnit.Texture0);
  388. }
  389. }
  390. #endregion
  391. #endregion
  392. #region Set2DRenderMatrix
  393. /// <summary>
  394. /// Set 2D render matrix, will set the worldHandle and
  395. /// worldViewProjectionHandle in a way that works for ES 1.1.
  396. /// </summary>
  397. public override void Set2DRenderMatrix()
  398. {
  399. if (shaderSupport)
  400. {
  401. base.Set2DRenderMatrix();
  402. }
  403. else
  404. {
  405. // Not already in 2D mode? Only then we need to set stuff anyway
  406. if (in2DMode == false)
  407. {
  408. in2DMode = true;
  409. // The world matrix is always identity (like in the base class).
  410. GL.MatrixMode(MatrixMode.Modelview);
  411. GL.LoadIdentity();
  412. // Note: viewInverseHandle is not used here, it makes no sense for 2D
  413. // rendering. It is mostly important for 3D camera position, etc.
  414. // We can directly set this to ScreenSpace.ViewProjection2D, this is
  415. // the only thing we care about, just the ProjectionMatrix because
  416. // World and View are always identity (all 2D data is already in
  417. // the desired projection space).
  418. GL.MatrixMode(MatrixMode.Projection);
  419. GL.LoadMatrix(ref ScreenSpace.InternalViewProjection2D.M11);
  420. }
  421. }
  422. }
  423. #endregion
  424. #region Set3DRenderMatrix
  425. /// <summary>
  426. /// Set 3D render matrix with bones, same as Set3DRenderMatrix, but also
  427. /// updates the bones for animated meshes.
  428. /// </summary>
  429. /// <param name="worldMatrix">Object world matrix for 3D rendering</param>
  430. /// <param name="skinnedBoneMatrices">Optional skinned matrices</param>
  431. public override void Set3DRenderMatrix(ref Matrix worldMatrix,
  432. Matrix[] skinnedBoneMatrices)
  433. {
  434. if (shaderSupport)
  435. {
  436. base.Set3DRenderMatrix(ref worldMatrix, skinnedBoneMatrices);
  437. }
  438. else
  439. {
  440. // Note: We changed the matrices, 2D mode needs to update them, but
  441. // this should rarely happen because we have different shaders.
  442. // Also note that 3D matrices always change, no need to cache or compare
  443. in2DMode = false;
  444. // The world matrix is always identity (like in the base class).
  445. GL.MatrixMode(MatrixMode.Modelview);
  446. GL.LoadMatrix(ref worldMatrix.M11);
  447. // Note: viewInverseHandle is not used here, it makes no sense for 2D
  448. // rendering. It is mostly important for 3D camera position, etc.
  449. // We can directly set this to ScreenSpace.ViewProjection2D, this is
  450. // the only thing we care about, just the ProjectionMatrix because
  451. // World and View are always identity (all 2D data is already in
  452. // the desired projection space).
  453. GL.MatrixMode(MatrixMode.Projection);
  454. // And set the ViewProjection3D, which perfectly fits into Projection
  455. GL.LoadMatrix(ref ScreenSpace.InternalViewProjection3D.M11);
  456. }
  457. }
  458. #endregion
  459. }
  460. }