1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.graphics.threedee.opengl.shader; 13 14 version(Have_derelict_gl3) { 15 16 import retrograde.graphics.core; 17 import retrograde.file; 18 import retrograde.math; 19 import retrograde.graphics.threedee.opengl.uniform; 20 import retrograde.graphics.threedee.opengl.rendering; 21 import retrograde.cache; 22 23 import std.string; 24 import std.typecons; 25 import std.conv; 26 import std.typecons; 27 import std.exception; 28 29 import derelict.opengl3.gl3; 30 31 import poodinis; 32 33 class ShaderValidationException : Exception { 34 mixin basicExceptionCtors; 35 } 36 37 class OpenGlShader : Shader { 38 private static immutable GLuint[ShaderType] shaderTypeMapping; 39 40 private GLuint shader; 41 42 shared static this() { 43 shaderTypeMapping = [ 44 ShaderType.vertexShader: GL_VERTEX_SHADER, 45 ShaderType.fragmentShader: GL_FRAGMENT_SHADER, 46 ShaderType.geometryShader: GL_GEOMETRY_SHADER, 47 ShaderType.tesselationControlShader: GL_TESS_CONTROL_SHADER, 48 ShaderType.tesselationEvaluationShader: GL_TESS_EVALUATION_SHADER, 49 ShaderType.computeShader: GL_COMPUTE_SHADER 50 ]; 51 } 52 53 this(File shaderFile, ShaderType type) { 54 super(shaderFile, type); 55 } 56 57 public override void compile() { 58 auto shaderSource = toStringz(shaderFile.readAsText()); 59 uint shaderType = getShaderType(); 60 shader = glCreateShader(shaderType); 61 glShaderSource(shader, 1, &shaderSource, null); 62 glCompileShader(shader); 63 verifyCompilationSuccess(); 64 } 65 66 private void verifyCompilationSuccess() { 67 GLint compileStatus; 68 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); 69 if (compileStatus == GL_FALSE) { 70 GLint logSize; 71 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize); 72 GLchar[] infoLog = new GLchar[logSize]; 73 glGetShaderInfoLog(shader, logSize, null, infoLog.ptr); 74 throw new ShaderCompilationException(format("Error while compiling OpenGL %s: %s", type, infoLog)); 75 } 76 } 77 78 private uint getShaderType() { 79 auto glType = type in shaderTypeMapping; 80 if (!glType) { 81 throw new ShaderCompilationException("Error while compiling OpenGL shader", new UnsupportedShaderTypeException(type)); 82 } 83 return *glType; 84 } 85 86 public override void destroy() { 87 glDeleteShader(shader); 88 } 89 } 90 91 class OpenGlShaderProgram : ShaderProgram { 92 private GLuint _program; 93 private bool[UniformBlock] boundUniformBlocks; 94 95 public UniformContainer uniforms = new UniformContainer(); 96 public UniformBlock[] uniformBlocks; 97 98 public @property GLuint program() { 99 return _program; 100 } 101 102 this(OpenGlShader[] shaders) { 103 super(cast(Shader[]) shaders); 104 } 105 106 public override void compile() { 107 _program = glCreateProgram(); 108 109 foreachGlShader((shader) { 110 shader.compile(); 111 glAttachShader(_program, shader.shader); 112 }); 113 114 glLinkProgram(_program); 115 verifyLinkSuccess(); 116 117 foreachGlShader((shader) { 118 shader.destroy(); 119 }); 120 121 _isCompiled = true; 122 } 123 124 private void foreachGlShader(void delegate(OpenGlShader) fn) { 125 foreach (shader; shaders) { 126 auto glShader = cast(OpenGlShader) shader; 127 if (glShader) { 128 fn(glShader); 129 } 130 } 131 } 132 133 public override void use() { 134 glUseProgram(program); 135 if (uniforms.uniformsAreUpdated) { 136 applyDefaultBlockUniformData(); 137 uniforms.clearUniformsUpdated(); 138 } 139 } 140 141 private void applyDefaultBlockUniformData() { 142 foreach (uniform; uniforms.getAll()) { 143 if (!uniform.isUpdated) { 144 continue; 145 } 146 147 GLint location = glGetUniformLocation(program, uniform.name.toStringz); 148 if (location == -1) { 149 continue; 150 } 151 152 if (uniform.type == UniformType.glFloat) { 153 glUniform1f(location, cast(GLfloat) uniform.values[0]); 154 continue; 155 } 156 157 if (uniform.type == UniformType.glDouble) { 158 glUniform1d(location, cast(GLdouble) uniform.values[0]); 159 continue; 160 } 161 162 if (uniform.type == UniformType.glInt) { 163 glUniform1i(location, cast(GLint) uniform.values[0]); 164 continue; 165 } 166 167 if (uniform.type == UniformType.glVec4) { 168 auto castedValueArray = to!(GLfloat[])(uniform.values); 169 glUniform4fv(location, 1, castedValueArray.ptr); 170 continue; 171 } 172 173 if (uniform.type == UniformType.glMat4) { 174 auto castedValueArray = to!(GLdouble[])(uniform.values); 175 glUniformMatrix4dv(location, 1, cast(GLboolean) false, castedValueArray.ptr); 176 continue; 177 } 178 } 179 } 180 181 public void validateUniforms() { 182 foreach (uniform; uniforms.getAll()) { 183 GLint location = glGetUniformLocation(program, uniform.name.toStringz); 184 if (location == -1) { 185 throw new ShaderValidationException("Uniform '" ~ uniform.name ~ "' is not defined or used in shader program."); 186 } 187 } 188 } 189 190 public void bindUniformBlock(UniformBlock uniformBlock) { 191 auto bound = uniformBlock in boundUniformBlocks; 192 if (bound is null || *bound != true) { 193 auto blockIndex = glGetUniformBlockIndex(program, uniformBlock.blockName.toStringz); 194 glUniformBlockBinding(program, blockIndex, uniformBlock.bindingPoint); 195 boundUniformBlocks[uniformBlock] = true; 196 } 197 } 198 199 private void verifyLinkSuccess() { 200 GLint linkStatus; 201 glGetProgramiv(_program, GL_LINK_STATUS, &linkStatus); 202 if (linkStatus == GL_FALSE) { 203 GLint logSize; 204 glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logSize); 205 GLchar[] infoLog = new GLchar[logSize]; 206 glGetProgramInfoLog(_program, logSize, null, infoLog.ptr); 207 throw new ShaderCompilationException(format("Error while linking OpenGL shader program: %s", infoLog)); 208 } 209 } 210 211 public override void destroy() { 212 glDeleteProgram(_program); 213 } 214 } 215 216 private alias ShaderSpecTuple = Tuple!(string, "name", ShaderType, "type"); 217 218 enum DefaultShader : ShaderSpecTuple { 219 vertexShader = ShaderSpecTuple("retrograde-vertex-shader.glsl", ShaderType.vertexShader), 220 fragmentShader = ShaderSpecTuple("retrograde-fragment-shader.glsl", ShaderType.fragmentShader) 221 } 222 223 alias DefaultShaderCache = Cache!(DefaultShader, OpenGlShader); 224 225 class DefaultShaderFactory { 226 227 private string[DefaultShader] shaderSources; 228 private DefaultShaderCache shaderCache = new DefaultShaderCache(); 229 230 public this() { 231 shaderSources[DefaultShader.vertexShader] = import(DefaultShader.vertexShader.name); 232 shaderSources[DefaultShader.fragmentShader] = import(DefaultShader.fragmentShader.name); 233 } 234 235 public OpenGlShader createShader(DefaultShader shader) { 236 return shaderCache.getOrAdd(shader, { 237 auto shaderSource = shaderSources[shader]; 238 return new OpenGlShader(new VirtualTextFile(shaderSource), shader.type); 239 }); 240 } 241 } 242 243 class DefaultShaderProgramFactory : CachedShaderProgramFactory 244 { 245 246 @Autowire 247 private OpenGlRenderSystem renderer; 248 249 @Autowire 250 private DefaultShaderFactory defaultShaderFactory; 251 252 protected override ShaderProgram create() 253 { 254 auto vertexShader = defaultShaderFactory.createShader(DefaultShader.vertexShader); 255 auto fragmentShader = defaultShaderFactory.createShader(DefaultShader.fragmentShader); 256 auto shaderProgram = new OpenGlShaderProgram([vertexShader, fragmentShader]); 257 shaderProgram.uniformBlocks = [renderer.retrogradeModelstateBlock]; 258 return shaderProgram; 259 } 260 } 261 262 }