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 }