1 module evael.graphics.shaders.Shader;
2 
3 import std.experimental.logger : errorf;
4 import std.exception : enforce;
5 import std.string : format, toStringz;
6 
7 import evael.graphics.GL;
8 
9 import evael.system.Asset;
10 
11 import evael.utils.Functions;
12 
13 /**
14  * Shader.
15  */
16 class Shader : IAsset
17 {
18 	private uint m_programID, m_vertexID, m_fragmentID, m_geometryID;
19 
20 	/// Shader name
21 	private string m_name;
22 
23 	/// Shader uniforms locations
24 	public int viewMatrix;
25     public int modelMatrix;
26     public int projectionMatrix;
27 
28 	/**
29 	 * Shader constructor.
30 	 */
31     public this(in uint programID, in uint vertexID, in uint fragmentID, in bool linked = true, in uint geometryID = 0)
32     {
33 		this.m_programID = programID;
34         this.m_vertexID = vertexID;
35         this.m_fragmentID = fragmentID;
36 		this.m_geometryID = geometryID;
37 
38 		this.viewMatrix = this.getUniformLocation("view");
39 		this.modelMatrix = this.getUniformLocation("model");
40         this.projectionMatrix = this.getUniformLocation("projection");
41     }
42 
43 	public this(Shader shader)
44 	{
45 		this(shader.programID, shader.vertexID, shader.fragmentID, true, shader.geometryID);
46 	}
47 
48 	@nogc
49 	public void dispose() const
50 	{
51 		gl.DeleteShader(this.m_vertexID);
52 		gl.DeleteShader(this.m_fragmentID);
53 
54 		if (this.m_geometryID)
55 		{
56 			gl.DeleteShader(this.m_geometryID);
57 		}
58 	}
59 
60 	public void link()
61 	{
62 		gl.LinkProgram(this.m_programID);
63 
64 		int linkStatus = 0;
65 		gl.GetProgramiv(this.m_programID, GL_LINK_STATUS, &linkStatus);
66 
67 		if (linkStatus != 1)
68 		{
69 			gl.GetProgramiv(this.m_programID, GL_INFO_LOG_LENGTH, &linkStatus);
70 
71 			char[] errors = new char[linkStatus];
72 
73 			gl.GetProgramInfoLog(this.m_programID, linkStatus, &linkStatus, errors.ptr);
74 
75 			throw new Exception("Cant link shader %s :\n %s".format(this.m_name, errors));
76 		}
77 	}
78 
79 	/**
80 	 * Returns location of an uniform variable.
81 	 * Params:
82 	 *		uniformName : uniform variable to retrieve
83 	 */
84 	protected int getUniformLocation(in string uniformName)
85 	{
86 		return gl.GetUniformLocation(this.m_programID, uniformName.toStringz());
87 	}
88 	
89 	/**
90 	 * Loads a shader.
91 	 * Params:
92 	 *		shaderName : shader to load
93 	 */
94     public static Shader load(in string shaderName, in bool linkProgram = true)
95     {
96 		import evael.utils.Config;
97 
98 		immutable string path = Config.Paths.shaders!string ~ shaderName;
99 
100 		immutable uint programId = gl.CreateProgram();
101 
102 		immutable vertexShaderId = createShader(programId, path ~ ".vert", ShaderType.Vertex);
103 		immutable fragmentShaderId = createShader(programId, path ~ ".frag", ShaderType.Fragment);
104 		immutable geometryShaderId = createShader(programId, path ~ ".geom", ShaderType.Geometry);
105 
106 		if (linkProgram)
107 		{
108 			gl.LinkProgram(programId);
109 		}
110 
111 		auto shader = new Shader(programId, vertexShaderId, fragmentShaderId, linkProgram, geometryShaderId);
112 		shader.name = shaderName;
113 
114 		return shader;
115     }
116 
117 	/**
118 	 * Generates and compiles a shader
119 	 * Params:
120 	 *		program : program ID
121 	 * 		fileName : shader to compile
122 	 * 		type : shader type(VS, FG, GS)
123 	 */
124     static uint createShader(in uint program, in string fileName, in uint type) 
125     {
126 		import std.file;
127 
128 		if (!exists(fileName))
129 		{
130 			errorf("Shader not found: %s", fileName);
131 			return -1;
132 		}
133 
134         immutable uint shader = gl.CreateShader(type);
135 
136         immutable string sourceCode = readText(fileName);
137 
138         enforce(sourceCode.length, "Shader %s is empty".format(fileName));
139 
140 		char* source = cast(char*)sourceCode.ptr;
141 
142 		// Shader compilation
143 		gl.ShaderSource(shader, 1, &source, [cast(int)sourceCode.length].ptr);
144 		gl.CompileShader(shader);
145 
146 		int compilationStatus;
147 		gl.GetShaderiv(shader, GL_COMPILE_STATUS, &compilationStatus);
148 
149 		// We check for compilation errors
150 		if (compilationStatus == false)
151 		{
152 			// Compilation failed, we retrieve error logs
153 			gl.GetShaderiv(shader, GL_INFO_LOG_LENGTH, &compilationStatus);
154 
155 			char[] errors = new char[compilationStatus];
156 
157 			gl.GetShaderInfoLog(shader, compilationStatus, &compilationStatus, errors.ptr);
158 
159 			throw new Exception("Shader %s cant be compiled :\n %s".format(fileName, errors));
160 		}
161 
162 		gl.AttachShader(program, shader);
163 
164 		return shader;
165     }
166 
167 	/**
168 	 * Properties
169 	 */
170 	@nogc
171     @property nothrow
172 	{
173 		public uint programID() const
174 		{
175 			return this.m_programID;
176 		}
177 
178 		public uint vertexID() const
179 		{
180 			return this.m_vertexID;
181 		}
182 
183 		public uint fragmentID() const
184 		{
185 			return this.m_fragmentID;
186 		}
187 
188 		public uint geometryID() const
189 		{
190 			return this.m_geometryID;
191 		}
192 
193 		public void name(in string value)
194 		{
195 			this.m_name = value;
196 		}
197 	}
198 }