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