PageRenderTime 943ms CodeModel.GetById 232ms app.highlight 354ms RepoModel.GetById 209ms app.codeStats 2ms

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