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.uniform;
13 
14 version(Have_derelict_gl3) {
15 
16 import retrograde.math;
17 import retrograde.graphics.threedee.opengl.shader;
18 import retrograde.graphics.threedee.opengl.error;
19 
20 import std.string;
21 import std.exception;
22 
23 import derelict.opengl3.gl3;
24 
25 import poodinis;
26 
27 enum UniformType {
28     glFloat,
29     glDouble,
30     glInt,
31     glVec4,
32     glMat4,
33     glBool
34 }
35 
36 struct Uniform {
37     public string name;
38     public UniformType type;
39     public double[] values;
40     public bool isUpdated = true;
41 
42     this(string name, UniformType type, double value) {
43         enforce(type != UniformType.glVec4, "Uniform constructor taking a single value cannot be used for vector uniforms. See other constructors.");
44         enforce(type != UniformType.glMat4, "Uniform constructor taking a single value cannot be used for matrix uniforms. See other constructors.");
45 
46         this.name = name;
47         this.type = type;
48         this.values = [value];
49     }
50 
51     this(string name, Vector4D vector) {
52         this.name = name;
53         this.type = UniformType.glVec4;
54         this.values = [vector.x, vector.y, vector.z, vector.w];
55     }
56 
57     this(string name, Matrix4D matrix) {
58         this.name = name;
59         this.type = UniformType.glMat4;
60 
61         for(uint columnIndex = 0; columnIndex < Matrix4D._Columns; columnIndex++) {
62             for(uint rowIndex = 0; rowIndex < Matrix4D._Rows; rowIndex++) {
63                 this.values ~= matrix[rowIndex, columnIndex];
64             }
65         }
66     }
67 
68     this(string name, bool boolean) {
69         this.name = name;
70         this.type = UniformType.glBool;
71         this.values = [boolean];
72     }
73 }
74 
75 class UniformContainer {
76     private Uniform[string] uniforms;
77     private bool _uniformsAreUpdated;
78 
79     public @property bool uniformsAreUpdated() {
80         return _uniformsAreUpdated;
81     }
82 
83     public @property size_t length() {
84         return uniforms.length;
85     }
86 
87     this(Uniform[] uniforms = []) {
88         foreach (uniform; uniforms) {
89             set(uniform);
90         }
91     }
92 
93     public void set(Uniform uniform) {
94         uniforms[uniform.name] = uniform;
95         _uniformsAreUpdated = true;
96     }
97 
98     public Uniform[] getAll() {
99         return uniforms.values;
100     }
101 
102     public string[] getAllNames() {
103         return uniforms.keys;
104     }
105 
106     public Uniform get(string uniformName) {
107         return uniforms[uniformName];
108     }
109 
110     public void clearUniforms() {
111         uniforms.clear;
112         _uniformsAreUpdated = true;
113     }
114 
115     public void clearUniformsUpdated() {
116         _uniformsAreUpdated = false;
117         foreach(uniform; uniforms.byValue()) {
118             uniform.isUpdated = false;
119         }
120     }
121 }
122 
123 class UniformBlock {
124     public string blockName;
125     public UniformBlockLayout layout;
126 
127     public bool hasBuffer = false;
128     public uint buffer;
129     public uint bindingPoint;
130 
131     public UniformContainer uniforms;
132 
133     this(string blockName, Uniform[] uniforms = [], UniformBlockLayout layout = UniformBlockLayout.sharedLayout) {
134         this.blockName = blockName;
135         this.layout = layout;
136         this.uniforms = new UniformContainer(uniforms);
137     }
138 }
139 
140 enum UniformBlockLayout {
141     std140Layout,
142     sharedLayout
143 //	packedLayout -- Not supported yet
144 }
145 
146 interface UniformBlockBuilder {
147     void buildData(UniformBlock uniformBlock, OpenGlShaderProgram shaderProgram);
148 }
149 
150 class SharedUniformBlockBuilder : UniformBlockBuilder {
151     //TODO: Reject packed uniform blocks and make a builder for it
152     //TODO: Reject std140 uniform blocks and make a builder for it
153 
154     private static uint nextAvailableBindingPoint = 0;
155 
156     @Autowire
157     private ErrorService errorService;
158 
159     public override void buildData(UniformBlock uniformBlock, OpenGlShaderProgram shaderProgram) {
160         if (uniformBlock is null || !uniformBlock.uniforms.uniformsAreUpdated) {
161             return;
162         }
163 
164         auto programHandle = shaderProgram.program;
165         auto uniformCount = uniformBlock.uniforms.length;
166         const(GLchar)*[] uniformNames;
167         foreach(uniformName; uniformBlock.uniforms.getAllNames()) {
168             uniformNames ~= (uniformBlock.blockName ~ "." ~ uniformName).toStringz;
169         }
170 
171         GLuint[] uniformIndices = new GLuint[uniformCount];
172         glGetUniformIndices(programHandle, uniformCount, uniformNames.ptr, uniformIndices.ptr);
173 
174         foreach(index, uniformIndex; uniformIndices) {
175             if (uniformIndex == GL_INVALID_INDEX) {
176                 throw new UniformBlockBuildException(format("Uniform index for uniform block member %s is invalid. Does it exist in the shader?", uniformNames[index]));
177             }
178         }
179 
180         GLint[] uniformOffsets = new GLint[uniformCount];
181         GLint[] arrayStrides = new GLint[uniformCount];
182         GLint[] matrixStrides = new GLint[uniformCount];
183         glGetActiveUniformsiv(programHandle, uniformCount, uniformIndices.ptr, GL_UNIFORM_OFFSET, uniformOffsets.ptr);
184         glGetActiveUniformsiv(programHandle, uniformCount, uniformIndices.ptr, GL_UNIFORM_ARRAY_STRIDE, arrayStrides.ptr);
185         glGetActiveUniformsiv(programHandle, uniformCount, uniformIndices.ptr, GL_UNIFORM_MATRIX_STRIDE, matrixStrides.ptr);
186 
187         GLint blockSize;
188         auto blockIndex = glGetUniformBlockIndex(programHandle, uniformBlock.blockName.toStringz);
189         glGetActiveUniformBlockiv(programHandle, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
190         GLubyte[] blockData = new GLubyte[blockSize];
191 
192         //TODO: Optimize. Only change what's been updated. Directly stream to GPU buffer.
193         foreach(index, uniform; uniformBlock.uniforms.getAll()) {
194             ubyte* uniformDataOffset = blockData.ptr + uniformOffsets[index];
195             if (uniform.type == UniformType.glFloat) {
196                 *(cast(float*) uniformDataOffset) = cast(float) uniform.values[0];
197             } else if (uniform.type == UniformType.glVec4) {
198                 (cast(float*) uniformDataOffset)[0] = cast(float) uniform.values[0];
199                 (cast(float*) uniformDataOffset)[1] = cast(float) uniform.values[1];
200                 (cast(float*) uniformDataOffset)[2] = cast(float) uniform.values[2];
201                 (cast(float*) uniformDataOffset)[3] = cast(float) uniform.values[3];
202             } else if (uniform.type == UniformType.glMat4) {
203                 foreach (i; 0 .. 4) {
204                     uint offset = matrixStrides[index] * i;
205                     foreach (j; 0 .. 4) {
206                         *(cast(float*) (uniformDataOffset + offset)) = cast(float) uniform.values[i * 4 + j];
207                         offset += float.sizeof;
208                     }
209                 }
210             } else if (uniform.type == UniformType.glBool) {
211                 *(cast(GLboolean*) uniformDataOffset) = cast(GLboolean) uniform.values[0];
212             } else {
213                 throw new UniformBlockBuildException(format("Uniform of type %s not supported in uniform block creation for block %s", uniform.type, uniformBlock.blockName));
214             }
215         }
216 
217         if (uniformBlock.hasBuffer) {
218             glNamedBufferSubData(uniformBlock.buffer, 0, blockSize, blockData.ptr);
219         } else {
220             GLint maxBlockBindings;
221             glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxBlockBindings);
222             if (nextAvailableBindingPoint > maxBlockBindings) {
223                 uniformBlock.uniforms.clearUniformsUpdated();
224                 throw new UniformBlockBuildException(format("No more block bindings available, maximum of %s reached.", maxBlockBindings));
225             }
226 
227             GLuint buffer;
228             glCreateBuffers(1, &buffer);
229             glNamedBufferData(buffer, blockSize, blockData.ptr, GL_DYNAMIC_DRAW);
230             auto bindingPoint = nextAvailableBindingPoint++;
231             glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);
232 
233             uniformBlock.buffer = buffer;
234             uniformBlock.bindingPoint = bindingPoint;
235             uniformBlock.hasBuffer = true;
236         }
237 
238         errorService.throwOnErrors!UniformBlockBuildException("building uniform block " ~ uniformBlock.blockName);
239 
240         uniformBlock.uniforms.clearUniformsUpdated();
241     }
242 }
243 
244 class UniformBlockBuildException : Exception {
245     mixin basicExceptionCtors;
246 }
247 
248 }