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 }