/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
- using System;
- using Delta.ContentSystem.Rendering.Helpers;
- using Delta.Engine;
- using Delta.Engine.Dynamic;
- using Delta.Graphics.BaseOpenGL;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Graphics.ShaderFeatures;
- using Delta.Utilities.Helpers;
- using OpenTK.Graphics.OpenGL;
-
- namespace Delta.Graphics.OpenTK
- {
- /// <summary>
- /// OpenTK shader implementation.
- /// </summary>
- internal class OpenTKShader : BaseOpenGLShader
- {
- #region Private
-
- #region lastActiveTextureUnit (Private)
- /// <summary>
- /// Simple cache to prevent setting the same active texture unit over
- /// and over again. Many times we only need the first unit anyways.
- /// </summary>
- private static TextureUnit lastActiveTextureUnit = TextureUnit.Texture0;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Creates a OpenTK shader with a given shader content name
- /// </summary>
- /// <param name="setShaderName">Shader content name</param>
- public OpenTKShader(string setShaderName)
- : base(setShaderName)
- {
- }
-
- /// <summary>
- /// Create a OpenTK shader with a given shader flags
- /// </summary>
- /// <param name="setShaderFlags">Shader flags to search for</param>
- public OpenTKShader(ShaderFeatureFlags setShaderFlags)
- : base(setShaderFlags)
- {
- }
- #endregion
-
- #region Render (Public)
- /// <summary>
- /// Draw something in 2D or 3D with this shader, should be called as few
- /// times as possible (rendering is much faster without many shader
- /// switches). This is currently ONLY used from the MaterialManager!
- /// Derived implementations MUST call base.Render(), and should only
- /// continue rendering if this method returns true
- /// </summary>
- /// <param name="renderDelegate">This is the delegate we pass from the
- /// MaterialManager, which will render all RenderGeometries with the
- /// pre-calculated geometries and their materials (we don't know anything
- /// about that here, it is all handled and sorted over there).</param>
- /// <returns></returns>
- public override bool Render(RunDelegate renderDelegate)
- {
- if (Graphic.Instance.HasShaderSupport)
- {
- if (ShaderHandle == MathHelper.InvalidIndex)
- {
- Log.Warning("Cannot render with uninitialized shader: " + this);
- return false;
- }
-
- // Use this shader program
- UseProgram();
- }
- else
- {
- if (data.UsesTexturing)
- {
- GL.Enable(EnableCap.Texture2D);
- }
- else
- {
- GL.Disable(EnableCap.Texture2D);
- }
- }
-
- // And render all geometry as setup in Delta.Rendering.MaterialManager
- renderDelegate();
-
- if (Graphic.Instance.HasShaderSupport == false)
- {
- GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
- GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
- OpenTKGraphic.DisableFFPVertexFormat(VertexFormat);
-
- // If we used something with color, we need to reset the color to white
- if (data.UsesVertexColoring)
- {
- GL.Color4(1.0f, 1.0f, 1.0f, 1.0f);
- }
- }
-
- return true;
- }
- #endregion
-
- #region Methods (Private)
-
- #region CompileShader
- /// <summary>
- /// Create a new shader, set the source and compile it.
- /// </summary>
- protected override bool CompileShader(bool isVertexShader,
- string shaderCode, ref int shader)
- {
- // We need to make sure that our graphic device is set, else this
- // whole class makes no sense!
- if (BaseOpenGLGraphic.IsValidAndCurrent == false)
- {
- throw new NotSupportedException(
- "OpenTKShader(" + data.Name +
- ") cannot be used when OpenTKGraphic has not been initialized!\n" +
- "There must be something wrong with creating the graphic device: " +
- Settings.Modules.GraphicModule +
- // Extra help text if the graphics module is not OpenTK!
- (Settings.Modules.GraphicModule.Contains("OpenTK") == false
- ? "\nYou problem is that you wanted to create " +
- Settings.Modules.GraphicModule +
- " graphics, but that failed or " +
- "was not found and you ended up with OpenTKShader, which needs " +
- "the OpenTKGraphic device to be up!"
- : ""));
- }
-
- shader = GL.CreateShader(
- isVertexShader
- ? ShaderType.VertexShader
- : ShaderType.FragmentShader);
-
- int length = shaderCode.Length;
- GL.ShaderSource(shader, shaderCode);
- GL.CompileShader(shader);
-
- int state = 0;
- GL.GetShader(shader, ShaderParameter.CompileStatus, out state);
-
- return state != 0;
- }
- #endregion
-
- #region GetShaderErrorLog
- /// <summary>
- /// Get shader info log, will not only get the error info
- /// log message, but also kill the shader itself.
- /// </summary>
- protected override string GetShaderErrorLog(int shader, bool killShader)
- {
- string errorLog = GL.GetShaderInfoLog(shader);
-
- // Delete the broken shader if this was a fatal error
- if (killShader)
- {
- GL.DeleteShader(shader);
- shader = MathHelper.InvalidIndex;
- }
-
- return errorLog;
- }
- #endregion
-
- #region CreateProgram
- /// <summary>
- /// Create program
- /// </summary>
- protected override int CreateProgram(int vertexShader,
- int fragmentShader)
- {
- int program = GL.CreateProgram();
- GL.AttachShader(program, vertexShader);
- GL.AttachShader(program, fragmentShader);
- return program;
- }
- #endregion
-
- #region LinkProgram
- /// <summary>
- /// Link program
- /// </summary>
- protected override bool LinkProgram()
- {
- if (Graphic.Instance.HasShaderSupport)
- {
- GL.LinkProgram(shaderHandle);
-
- int linkStatus;
- GL.GetProgram(shaderHandle, ProgramParameter.LinkStatus, out linkStatus);
- return linkStatus != 0;
- }
- return true;
- }
- #endregion
-
- #region UseProgram
- /// <summary>
- /// Use program
- /// </summary>
- protected override void UseProgram()
- {
- // Note: We need to use the ShaderHandle property here to force loading
- // the shader if we have not done yet. All calls after this will assume
- // that the shader has been loaded (all properties, draw calls, etc.)
- GL.UseProgram(ShaderHandle);
- }
- #endregion
-
- #region GetProgramErrorLog
- /// <summary>
- /// Get program error log
- /// </summary>
- protected override string GetProgramErrorLog()
- {
- int length;
- GL.GetProgram(shaderHandle, ProgramParameter.InfoLogLength, out length);
- string infoLog = GL.GetProgramInfoLog(shaderHandle);
-
- ErrorCode error = GL.GetError();
- if (error != ErrorCode.NoError)
- {
- Log.Warning("There was an OpenGL error while creating the shader " +
- "'" + this + "': " + error);
- }
-
- return infoLog;
- }
- #endregion
-
- #region DeleteProgram
- /// <summary>
- /// Delete the shader program.
- /// </summary>
- protected override void DeleteProgram()
- {
- GL.DeleteProgram(shaderHandle);
- }
- #endregion
-
- #region GetAttribLocation
- /// <summary>
- /// GetAttribLocation implementation.
- /// </summary>
- protected override int GetAttribLocation(string name)
- {
- return GL.GetAttribLocation(shaderHandle, name);
- }
- #endregion
-
- #region GetUniformHandle
- /// <summary>
- /// Get uniform location
- /// </summary>
- protected override object GetUniformHandle(ShaderUniformNames name)
- {
- int uniformId = GL.GetUniformLocation(shaderHandle, name.ToString());
- // Note: If uniform was not found, do not return -1, return null instead.
- // This way the base Shader class can optimize all properties easily
- // with a unequal to null check (can't be an int because of other
- // graphic platforms that do not have integers for shader parameters).
- return uniformId != MathHelper.InvalidIndex
- ? (object)uniformId
- : null;
- }
- #endregion
-
- #region SetUniform
- /// <summary>
- /// Set uniform float (for some special settings, e.g. fog, time, etc.).
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, float value)
- {
- GL.Uniform1((int)uniformId, value);
- }
-
- /// <summary>
- /// Set uniform Vector (for some advanced shaders)
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, Vector value)
- {
- GL.Uniform3((int)uniformId, value.X, value.Y, value.Z);
- }
-
- /// <summary>
- /// Set uniform Matrix (for world, viewInverse and worldViewProj).
- /// A boxed reference value is used here to increase performance.
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, ref Matrix value)
- {
- GL.UniformMatrix4((int)uniformId, 1, false, ref value.M11);
- }
-
- /// <summary>
- /// Set uniform Matrix array (for skinning)
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, Matrix[] value)
- {
- // Set all matrices at once :)
- GL.UniformMatrix4((int)uniformId, value.Length, false, ref value[0].M11);
- }
-
- /// <summary>
- /// Set uniform Color (for material colors)
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, Color value)
- {
- // We can just use the uint version of Uniform1 to set all 4 bytes!
- //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);
- // Using the int version instead.
- //crashes with InvalidOperation exception, which is even worse (happens only in debug, in release mode we get some random gl error much later):
- // GL.Uniform1((int)uniformId, (int)value.PackedRGBA);
- //crashes too with InvalidOperation because vec4 is not 4 bytes:
- //.RedByte, value.GreenByte, value.BlueByte, value.AlphaByte);
- //good help: http://www.opengl.org/sdk/docs/man/xhtml/glUniform.xml
-
- // This works, might not be the most efficient, but it works :)
- GL.Uniform4((int)uniformId, value.R, value.G, value.B, value.A);
- }
-
- /// <summary>
- /// Set uniform Point (rarely used (PostScreenWindowSize for example))
- /// Note: Only called if the given uniform handle id is valid.
- /// </summary>
- protected override void SetUniform(object uniformId, Point value)
- {
- GL.Uniform2((int)uniformId, value.X, value.Y);
- }
-
- /// <summary>
- /// Set uniform
- /// </summary>
- protected override void SetUniform(object uniformHandle,
- BaseTexture value)
- {
- if (uniformHandle != null &&
- value != null)
- {
- int imageHande = 0;
- BaseOpenGLTexture glImage = value as BaseOpenGLTexture;
- if (glImage != null)
- {
- imageHande = glImage.TextureHandle;
- }
- else
- {
- BaseOpenGLRenderToTexture glTexture =
- value as BaseOpenGLRenderToTexture;
- if (glTexture != null)
- {
- imageHande = glTexture.TextureHandle;
- }
- else
- {
- Log.Warning("The given value: '" + value + "' is not supported " +
- "in OpenGL.");
- }
- }
-
- TextureUnit unit = TextureUnit.Texture0;
- if (uniformHandle == normalMapHandle)
- {
- unit = TextureUnit.Texture1;
- }
- // We currently only support either specular maps or detail maps.
- else if (uniformHandle == specularMapHandle)
- {
- unit = TextureUnit.Texture2;
- }
- else if (uniformHandle == shaderLutTextureHandle)
- {
- // If we have a specular map already, use the next texture.
- // Note: Lightmaps will not work this way (overrides texture)!
- if (specularMapHandle != null)
- {
- unit = TextureUnit.Texture3;
- }
- else
- {
- unit = TextureUnit.Texture2;
- }
- }
- else if (uniformHandle == heightMapHandle)
- {
- // Height map is only allowed as texture unit 2 (no specular)
- unit = TextureUnit.Texture2;
- }
- // LightMaps are currently always in the last texture unit!
- else if (uniformHandle == lightMapHandle)
- {
- unit = TextureUnit.Texture3;
- }
- else if (uniformHandle == shadowMapTextureHandle)
- {
- //0, 1, 2, 3 can all be used, e.g. NormalMapParallaxLightMap,
- // but we still want shadow for that too.
- unit = TextureUnit.Texture4;
- }
- else if (uniformHandle == detailMapHandle)
- {
- // Use unit 5 as our texture
- unit = TextureUnit.Texture5;
- }
- // Note: We only need to change the active texture channel if it
- // changes. This is usually the case when setting complex shaders,
- // but many times we just need the first texture channel.
- if (unit != lastActiveTextureUnit)
- {
- lastActiveTextureUnit = unit;
- GL.ActiveTexture(unit);
- }
- GL.BindTexture(TextureTarget.Texture2D, imageHande);
- GL.Uniform1((int)uniformHandle, unit - TextureUnit.Texture0);
- }
- }
- #endregion
-
- #endregion
-
- #region Set2DRenderMatrix
- /// <summary>
- /// Set 2D render matrix, will set the worldHandle and
- /// worldViewProjectionHandle in a way that works for ES 1.1.
- /// </summary>
- public override void Set2DRenderMatrix()
- {
- if (shaderSupport)
- {
- base.Set2DRenderMatrix();
- }
- else
- {
- // Not already in 2D mode? Only then we need to set stuff anyway
- if (in2DMode == false)
- {
- in2DMode = true;
-
- // The world matrix is always identity (like in the base class).
- GL.MatrixMode(MatrixMode.Modelview);
- GL.LoadIdentity();
-
- // Note: viewInverseHandle is not used here, it makes no sense for 2D
- // rendering. It is mostly important for 3D camera position, etc.
- // We can directly set this to ScreenSpace.ViewProjection2D, this is
- // the only thing we care about, just the ProjectionMatrix because
- // World and View are always identity (all 2D data is already in
- // the desired projection space).
- GL.MatrixMode(MatrixMode.Projection);
- GL.LoadMatrix(ref ScreenSpace.InternalViewProjection2D.M11);
- }
- }
- }
- #endregion
-
- #region Set3DRenderMatrix
- /// <summary>
- /// Set 3D render matrix with bones, same as Set3DRenderMatrix, but also
- /// updates the bones for animated meshes.
- /// </summary>
- /// <param name="worldMatrix">Object world matrix for 3D rendering</param>
- /// <param name="skinnedBoneMatrices">Optional skinned matrices</param>
- public override void Set3DRenderMatrix(ref Matrix worldMatrix,
- Matrix[] skinnedBoneMatrices)
- {
- if (shaderSupport)
- {
- base.Set3DRenderMatrix(ref worldMatrix, skinnedBoneMatrices);
- }
- else
- {
- // Note: We changed the matrices, 2D mode needs to update them, but
- // this should rarely happen because we have different shaders.
- // Also note that 3D matrices always change, no need to cache or compare
- in2DMode = false;
-
- // The world matrix is always identity (like in the base class).
- GL.MatrixMode(MatrixMode.Modelview);
- GL.LoadMatrix(ref worldMatrix.M11);
-
- // Note: viewInverseHandle is not used here, it makes no sense for 2D
- // rendering. It is mostly important for 3D camera position, etc.
- // We can directly set this to ScreenSpace.ViewProjection2D, this is
- // the only thing we care about, just the ProjectionMatrix because
- // World and View are always identity (all 2D data is already in
- // the desired projection space).
- GL.MatrixMode(MatrixMode.Projection);
- // And set the ViewProjection3D, which perfectly fits into Projection
- GL.LoadMatrix(ref ScreenSpace.InternalViewProjection3D.M11);
- }
- }
- #endregion
- }
- }