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