/*============================================================================= Project: SharpImage Module: siOpenGlShader.cs Language: C# Author: Dan Mueller Date: $Date: 2007-07-06 10:57:00 +1000 (Fri, 06 Jul 2007) $ Revision: $Revision: 2 $ Copyright (c) Queensland University of Technology (QUT) 2007. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =============================================================================*/ using System; using System.IO; using System.Text; using System.Drawing; using System.Collections; using System.Diagnostics; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Reflection; using System.Reflection.Emit; using Tao.OpenGl; using Tao.FreeGlut; using Tao.Platform.Windows; namespace SharpImage.Rendering { /// /// Encapsulates a vertex and fragment shading program in OGSL (OpenGL Shading Language). /// public class siOpenGlShader { #region Instance Variables //============================================================================ private int m_VertexHandle = 0; private int m_FragmentHandle = 0; private int m_ProgramHandle = 0; //============================================================================ #endregion #region Construction and Disposal //============================================================================ /// /// Default constructor. /// public siOpenGlShader() { } //============================================================================ #endregion #region Properties //============================================================================ #region VertexProgramPath //============================================================================ private String m_VertexProgramPath = string.Empty; /// /// Gets the vertex shader program absolute path and filename. /// public String VertexProgramPath { get { return this.m_VertexProgramPath; } set { this.m_VertexProgramPath = value; } } //============================================================================ #endregion #region FragmentProgramPath //============================================================================ private String m_FragmentProgramPath = string.Empty; /// /// Gets the fragment shader program absolute path and filename. /// public String FragmentProgramPath { get { return this.m_FragmentProgramPath; } set { this.m_FragmentProgramPath = value; } } //============================================================================ #endregion #region IsLoaded //============================================================================ private bool m_IsLoaded = false; /// /// Gets if the program has been loaded. /// public bool IsLoaded { get { return this.m_IsLoaded; } } //============================================================================ #endregion #region IsCompiled //============================================================================ private bool m_IsCompiled = false; /// /// Gets if the program has been compiled. /// public bool IsCompiled { get { return this.m_IsCompiled; } } //============================================================================ #endregion #region IsAttached //============================================================================ private bool m_IsAttached = false; /// /// Gets if the program has been attached. /// public bool IsAttached { get { return this.m_IsAttached; } } //============================================================================ #endregion #region IsLinked //============================================================================ private bool m_IsLinked = false; /// /// Gets if the program has been linked. /// public bool IsLinked { get { return this.m_IsLinked; } } //============================================================================ #endregion #region IsReady //============================================================================ /// /// Gets if the program is ready for use. /// public bool IsReady { get { return this.IsLoaded && this.IsCompiled && this.IsLinked; } } //============================================================================ #endregion //============================================================================ #endregion #region Main Methods //============================================================================ /// /// Initialises the vertex and fragment shader programs for use. /// This method MUST be called before the programs can be used (via Begin/End). /// An exception is thrown on failure. /// /// The absolute path to the vertex program source. /// The absolute path to the fragment program source. public void Initialise(String pathVertex, String pathFragment) { // Set member variables this.m_VertexProgramPath = pathVertex; this.m_FragmentProgramPath = pathFragment; // Do the work this.Initialise(); } /// /// Initialises the vertex and fragment shader programs for use. /// Uses the VertexProgramPath and FragmentProgramPath variables. /// This method MUST be called before the programs can be used (via Begin/End). /// An exception is thrown on failure. /// public void Initialise() { // Do the init work this.LoadExtensions(); this.LoadPrograms(this.m_VertexProgramPath, this.m_FragmentProgramPath); this.CompilePrograms(); this.AttachPrograms(); this.LinkPrograms(); // Check init suceeded if (!this.IsReady) throw new ApplicationException("Could not initialise the vertex and fragment shader programs."); } /// /// Specifies that OpenGL should switch from the fixed lighting functionality to the /// functionality specified by the vertex and fragment shader programs. /// public void Begin() { if (!this.IsReady) throw new ApplicationException("The vertex and fragment shader programs are not ready for use."); Gl.glUseProgramObjectARB(this.m_ProgramHandle); } /// /// Specifies that OpenGL should switch back to the fixed functionality. /// public void End() { if (!this.IsReady) throw new ApplicationException("The vertex and fragment shader programs are not ready for use."); Gl.glUseProgramObjectARB(0); } //============================================================================ #endregion #region Uniforms, Attributes, Varying //============================================================================ /// /// Sets the given sampler to the given texture unit. /// /// The name of the sampler variable in the program. /// The texture unit value. public void SetTextureUnit(string sampler, int texunit) { this.SetUniformInt(sampler, texunit); } /// /// Sets the given uniform (type vec4) to the given values. /// /// The name of the vec4 uniform variable. /// /// /// /// public void SetUniformVec4(string uniform, float v0, float v1, float v2, float v3) { int location = Gl.glGetUniformLocationARB(this.m_ProgramHandle, uniform); if (location != -1) { this.Begin(); Gl.glUniform4fARB(location, v0, v1, v2, v3); this.End(); } } /// /// Sets the given uniform (type vec3) to the given values. /// /// The name of the vec3 uniform variable. /// /// /// public void SetUniformVec3(string uniform, float v0, float v1, float v2) { int location = Gl.glGetUniformLocationARB(this.m_ProgramHandle, uniform); if (location != -1) { this.Begin(); Gl.glUniform3fARB(location, v0, v1, v2); this.End(); } } /// /// Sets the given uniform (type int) to the given value. /// /// The name of the int uniform variable /// public void SetUniformInt(string uniform, int v0) { int location = Gl.glGetUniformLocationARB(this.m_ProgramHandle, uniform); if (location != -1) { this.Begin(); Gl.glUniform1iARB(location, v0); this.End(); } } /// /// Sets the given uniform (type float) to the given value. /// /// The name of the int uniform variable /// The value public void SetUniformFloat(string uniform, float v0) { int location = Gl.glGetUniformLocationARB(this.m_ProgramHandle, uniform); if (location != -1) { this.Begin(); Gl.glUniform1fARB(location, v0); this.End(); } } //============================================================================ #endregion #region Private Methods //============================================================================ /// /// Loads the required extensions. /// private void LoadExtensions() { // List the extensions to load string[] extensionsToLoad = new string[] { "GL_ARB_fragment_shader", "GL_ARB_shader_objects" }; // Try to load the extensions bool result = true; foreach (string extensionToLoad in extensionsToLoad) result &= GlExtensionLoader.LoadExtension(extensionToLoad); if (!result) { string formattedExtensionsToLoad = string.Empty; for (int i = 0; i < extensionsToLoad.Length; i++) formattedExtensionsToLoad += extensionsToLoad[i] + ", "; throw new ApplicationException("The OpenGL extensions required for fragment shader programs are not available: " + formattedExtensionsToLoad.Trim(',', ' ')); } } /// /// Loads a shader program using the given vertex and fragment shader code. /// An exception is thrown on failure. /// /// The absolute path to the vertex program source. /// The absolute path to the fragment program source. private void LoadPrograms(string pathVertex, string pathFragment) { // Check file paths if (pathVertex == null || pathVertex.Length == 0 || !File.Exists(pathVertex)) throw new FileNotFoundException("The vertex shader program file is not valid.", pathVertex); if (pathFragment == null || pathFragment.Length == 0 || !File.Exists(pathFragment)) throw new FileNotFoundException("The fragment shader program file is not valid.", pathFragment); // Read files StreamReader readerVertex = File.OpenText(pathVertex); string contentsVertex = readerVertex.ReadToEnd(); int lengthVertex = contentsVertex.Length; StreamReader readerFragment = File.OpenText(pathFragment); string contentsFragment = readerFragment.ReadToEnd(); int lengthFragment = contentsFragment.Length; // Create handles this.m_VertexHandle = Gl.glCreateShaderObjectARB(Gl.GL_VERTEX_SHADER_ARB); this.m_FragmentHandle = Gl.glCreateShaderObjectARB(Gl.GL_FRAGMENT_SHADER_ARB); // Load both programs Gl.glShaderSourceARB(this.m_VertexHandle, 1, ref contentsVertex, ref lengthVertex); Gl.glShaderSourceARB(this.m_FragmentHandle, 1, ref contentsFragment, ref lengthFragment); // Set that we have loaded the programs this.m_IsLoaded = true; } /// /// Compiles the shader program (vertex and fragment programs). /// An exception is thrown on failure. /// private void CompilePrograms() { // Check the programs have been loaded if (!this.IsLoaded) throw new ApplicationException("The programs have not been loaded."); // Compile both programs this.CompileProgram("vertex", this.m_VertexHandle); this.CompileProgram("fragment", this.m_FragmentHandle); // Set that we have compiled this.m_IsCompiled = true; } /// /// Compiles the given program. An exception is thrown on failure. /// /// Either "vertex" or "fragment". /// /// private void CompileProgram(string type, int handle) { //Compile Gl.glCompileShaderARB(handle); //Get result int result = 0; int lenLog = 0; Gl.glGetObjectParameterivARB(handle, Gl.GL_OBJECT_COMPILE_STATUS_ARB, out result); Gl.glGetObjectParameterivARB(handle, Gl.GL_OBJECT_INFO_LOG_LENGTH_ARB, out lenLog); //Show log if compilation failed if (result == 0) { // Get the log int lenLogActual = 0; string strLog = new string((char)0, 0); IntPtr ptrLog = Marshal.StringToHGlobalAnsi(strLog); Gl.glGetInfoLogARB(handle, lenLog, out lenLogActual, ptrLog); strLog = Marshal.PtrToStringAnsi(ptrLog); // Format the log string formattedLog = strLog.Replace("\n", "\r\n").ToString().TrimEnd('\r', '\n'); if (formattedLog.Length < 1) formattedLog = "Log not available."; // Create a message string message = "An error occured compiling the " + type + " shader program.\r\n"; message += "Handle=" + handle + "\r\n"; message += formattedLog; // Throw exception throw new ApplicationException(message); } } /// /// Attaches the vertex and fragment programs to a global shader program. /// An exception is thrown on failure. /// private void AttachPrograms() { // Check the programs have been compiled if (!this.IsCompiled) throw new ApplicationException("The programs have not been compiled."); // Create container object this.m_ProgramHandle = Gl.glCreateProgramObjectARB(); // Attach Gl.glAttachObjectARB(this.m_ProgramHandle, this.m_VertexHandle); Gl.glAttachObjectARB(this.m_ProgramHandle, this.m_FragmentHandle); // Set that we have attached this.m_IsAttached = true; } /// /// Links the program. An exception with the log information is thrown on failure. /// private void LinkPrograms() { // Check the programs have been attached if (!this.IsAttached) throw new ApplicationException("The programs have not been attached."); // Link Gl.glLinkProgramARB(this.m_ProgramHandle); // Get result int result = 0; int lenLog = 0; Gl.glGetObjectParameterivARB(this.m_ProgramHandle, Gl.GL_OBJECT_LINK_STATUS_ARB, out result); Gl.glGetObjectParameterivARB(this.m_ProgramHandle, Gl.GL_OBJECT_INFO_LOG_LENGTH_ARB, out lenLog); // Check if the link was successful if (result == 1) { this.m_IsLinked = true; } else { // Get the log int lenLogActual = 0; string strLog = new string((char)0, 0); IntPtr ptrLog = Marshal.StringToHGlobalAnsi(strLog); Gl.glGetInfoLogARB(this.m_ProgramHandle, lenLog, out lenLogActual, ptrLog); strLog = Marshal.PtrToStringAnsi(ptrLog); // Format the log string formattedLog = strLog.Replace("\n", "\r\n").ToString().TrimEnd('\r', '\n'); if (formattedLog.Length < 1) formattedLog = "Log not available."; // Create a message string message = "An error occured linking the vertex and fragment shader program.\r\n"; message += "ProgramHandle=" + this.m_ProgramHandle.ToString() + " VertexHandle=" + this.m_VertexHandle.ToString() + " FragmentHandle=" + this.m_FragmentHandle.ToString() + "\r\n"; message += formattedLog; // Throw exception throw new ApplicationException(message); } } //============================================================================ #endregion } }