/*============================================================================= Project: SharpImage Module: siOpenGlShader.cs Language: C# Author: Dan Mueller Date: $Date: 2007-09-06 16:19:18 +1000 (Thu, 06 Sep 2007) $ Revision: $Revision: 24 $ 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.TestExtensionsSupported(); 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 //============================================================================ /// /// Test if the required OpenGl extensions are available on the system. /// If they are not available, an exception is thrown. /// private void TestExtensionsSupported() { // List the extensions to test string[] extensionsToTest = new string[] { "GL_ARB_fragment_shader", "GL_ARB_shader_objects" }; // Test the extensions bool result = true; foreach (string extensionToTest in extensionsToTest) result &= Gl.IsExtensionSupported(extensionToTest); // Throw exception if (!result) { string formattedExtensionsToTest = string.Empty; for (int i = 0; i < extensionsToTest.Length; i++) formattedExtensionsToTest += extensionsToTest[i] + ", "; throw new ApplicationException("The OpenGL extensions required for fragment shader programs are not available: " + formattedExtensionsToTest.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 = new string[] { readerVertex.ReadToEnd() }; int[] lengthVertex = new int[] { contentsVertex[0].Length }; StreamReader readerFragment = File.OpenText(pathFragment); string[] contentsFragment = new string[] { readerFragment.ReadToEnd() }; int[] lengthFragment = new int[] { contentsFragment[0].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, contentsVertex, lengthVertex); Gl.glShaderSourceARB(this.m_FragmentHandle, 1, contentsFragment, 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 = new int[] { 0 }; int[] lenLog = new int[] { 0 }; Gl.glGetObjectParameterivARB(handle, Gl.GL_OBJECT_COMPILE_STATUS_ARB, result); Gl.glGetObjectParameterivARB(handle, Gl.GL_OBJECT_INFO_LOG_LENGTH_ARB, lenLog); //Show log if compilation failed if (result[0] == 0) { // Get the log int[] lenLogActual = new int[] { 0 }; StringBuilder strLog = new StringBuilder(255); Gl.glGetInfoLogARB(handle, lenLog[0], lenLogActual, strLog); // Format the log string formattedLog = strLog.ToString().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 = new int[] { 0 }; int[] lenLog = new int[] { 0 }; Gl.glGetObjectParameterivARB(this.m_ProgramHandle, Gl.GL_OBJECT_LINK_STATUS_ARB, result); Gl.glGetObjectParameterivARB(this.m_ProgramHandle, Gl.GL_OBJECT_INFO_LOG_LENGTH_ARB, lenLog); // Check if the link was successful if (result[0] == 1) { this.m_IsLinked = true; } else { // Get the log int[] lenLogActual = new int[] { 0 }; StringBuilder strLog = new StringBuilder(255); Gl.glGetInfoLogARB(this.m_ProgramHandle, lenLog[0], lenLogActual, strLog); // Format the log string formattedLog = strLog.ToString().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 } }