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 }