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.rendering; 13 14 version(Have_derelict_gl3) { 15 16 import retrograde.entity; 17 import retrograde.graphics.core; 18 import retrograde.file; 19 import retrograde.model; 20 import retrograde.graphics.threedee.opengl.shader; 21 import retrograde.graphics.threedee.opengl.model; 22 import retrograde.graphics.threedee.opengl.uniform; 23 import retrograde.math; 24 import retrograde.camera; 25 import retrograde.geometry; 26 import retrograde.graphics.threedee.opengl.error; 27 import retrograde.viewport; 28 29 import poodinis; 30 31 import derelict.opengl3.gl3; 32 33 import std.string; 34 import std.algorithm; 35 import std.experimental.logger; 36 import std.exception; 37 38 class OpenGl3RenderSystemInitException : Exception { 39 this(string reason) { 40 super("Could not initialize OpenGL render system: " ~ reason); 41 } 42 } 43 44 class OpenGlRenderSystem : EntityProcessor { 45 private const GLVersion minimumOpenGlVersion = GLVersion.GL45; 46 private const string uniformModelViewProjectionName = "modelViewProjection"; 47 private const string uniformIsTexturedName = "isTextured"; 48 private static const uint[TextureFilterMode] textureFilterModeMapping; 49 50 private GLVersion loadedOpenGlVersion; 51 private UniformBlock _retrogradeFramestateBlock = new UniformBlock("RetrogradeFramestate"); 52 private UniformBlock _retrogradeModelstateBlock = new UniformBlock("RetrogradeModelstate"); 53 private Matrix4D projectionMatrix; 54 private Entity cameraEntity; 55 private GLuint globalSampler; 56 private TextureFilterMode _globalTextureFilteringMode = TextureFilterMode.trilinear; 57 private Viewport viewport; 58 59 @Autowire 60 private ShaderProgramFactory[] shaderProgramFactories; 61 62 @Autowire 63 private SharedUniformBlockBuilder sharedUniformBlockBuilder; 64 65 @Autowire 66 private Logger log; 67 68 @Autowire 69 private ErrorService errorService; 70 71 @Autowire 72 private ViewportFactory viewportFactory; 73 74 public @property UniformBlock retrogradeFramestateBlock() { 75 return _retrogradeFramestateBlock; 76 } 77 78 public @property UniformBlock retrogradeModelstateBlock() { 79 return _retrogradeModelstateBlock; 80 } 81 82 public @property void globalTextureFilteringMode(TextureFilterMode textureFilterMode) { 83 this._globalTextureFilteringMode = textureFilterMode; 84 if (globalSampler) { 85 glSamplerParameteri(globalSampler, GL_TEXTURE_MIN_FILTER, textureFilterModeMapping[globalTextureFilteringMode]); 86 glSamplerParameteri(globalSampler, GL_TEXTURE_MAG_FILTER, textureFilterModeMapping[globalTextureFilteringMode]); 87 } 88 } 89 90 public @property TextureFilterMode globalTextureFilteringMode() { 91 return _globalTextureFilteringMode; 92 } 93 94 public static this() { 95 textureFilterModeMapping = [ 96 TextureFilterMode.nearestNeighbor: GL_NEAREST, 97 TextureFilterMode.linear: GL_LINEAR, 98 TextureFilterMode.trilinear: GL_LINEAR_MIPMAP_LINEAR 99 ]; 100 } 101 102 public override void initialize() { 103 viewport = viewportFactory.create(); 104 105 loadedOpenGlVersion = DerelictGL3.reload(minimumOpenGlVersion); 106 107 //TODO: Log loaded GL version 108 109 glEnable(GL_CULL_FACE); 110 glEnable(GL_DEPTH_TEST); 111 glDepthFunc(GL_LEQUAL); 112 113 initializeShaderPrograms(); 114 initializeSamplers(); 115 updateProjectionMatrix(); // TODO: Only update on resize of window/context 116 117 errorService.throwOnErrors!OpenGl3RenderSystemInitException; 118 } 119 120 private void initializeSamplers() { 121 glCreateSamplers(1, &globalSampler); 122 glSamplerParameteri(globalSampler, GL_TEXTURE_MIN_FILTER, textureFilterModeMapping[globalTextureFilteringMode]); 123 auto magFilter = globalTextureFilteringMode == TextureFilterMode.trilinear ? GL_LINEAR : textureFilterModeMapping[globalTextureFilteringMode]; 124 glSamplerParameteri(globalSampler, GL_TEXTURE_MAG_FILTER, magFilter); 125 glBindSampler(0, globalSampler); 126 } 127 128 private void initializeShaderPrograms() { 129 foreach (factory; shaderProgramFactories) { 130 auto shaderProgram = cast(OpenGlShaderProgram) factory.createShaderProgram(); 131 if (shaderProgram && !shaderProgram.isCompiled) { 132 shaderProgram.compile(); 133 } 134 } 135 } 136 137 protected override void processAcceptedEntity(Entity entity) { 138 if (entity.hasComponent!CameraComponent) { 139 cameraEntity = entity; 140 } 141 } 142 143 protected override void processRemovedEntity(Entity entity) { 144 if (entity is cameraEntity) { 145 cameraEntity = null; 146 } 147 } 148 149 public override void draw() { 150 GLfloat[] clearColor = [0.25, 0.25, 0.25, 1]; 151 glClearBufferfv(GL_COLOR, 0, clearColor.ptr); 152 glClearBufferfi(GL_DEPTH_STENCIL, 0, 1, 0); 153 154 auto viewProjectionMatrix = projectionMatrix * createViewMatrix(); 155 156 foreach (entity; _entities.getAll()) { 157 if (entity is cameraEntity) { 158 continue; 159 } 160 161 auto position = entity.getFromComponent!Position3DComponent(c => c.position, Vector3D(0, 0, 0)); 162 auto orientation = entity.getFromComponent!OrientationR3Component(c => c.orientation, QuaternionD.nullRotation); 163 auto scale = entity.getFromComponent!Scale3DComponent(c => c.scale, Vector3D(1, 1, 1)); 164 auto modelMatrix = position.toTranslationMatrix() * orientation.toRotationMatrix() * scale.toScalingMatrix(); 165 _retrogradeModelstateBlock.uniforms.set(Uniform(uniformModelViewProjectionName, viewProjectionMatrix * modelMatrix)); 166 167 auto model = cast(OpenGlModel) entity.getFromComponent!ModelComponent(c => c.model); 168 if (!model.isLoadedIntoVram()) { 169 model.loadIntoVram(); 170 errorService.logErrorsIfAny(); 171 } 172 173 bool hasTexture = false; 174 if (entity.hasComponent!TextureComponent) { 175 auto texture = cast(OpenGlTexture) entity.getFromComponent!TextureComponent(c => c.texture); 176 if (texture) { 177 if (!texture.isLoadedIntoVram()) { 178 texture.loadIntoVram(); 179 errorService.logErrorsIfAny(); 180 } 181 182 hasTexture = true; 183 texture.applyTexture(); 184 } 185 } 186 187 _retrogradeModelstateBlock.uniforms.set(Uniform(uniformIsTexturedName, hasTexture)); 188 189 if (entity.hasComponent!ShaderProgramComponent) { 190 auto shaderProgram = cast(OpenGlShaderProgram) entity.getFromComponent!ShaderProgramComponent(c => c.shaderProgram); 191 if (shaderProgram) { 192 useShaderProgram(shaderProgram); 193 } 194 } else { 195 glUseProgram(0); 196 } 197 198 model.draw(); 199 } 200 201 viewport.swapBuffers(); 202 } 203 204 private Matrix4D createViewMatrix() { 205 if (!cameraEntity) { 206 return Matrix4D.identity; 207 } 208 209 auto position = cameraEntity.getFromComponent!Position3DComponent(c => c.position, Vector3D(0)); 210 211 if (cameraEntity.hasComponent!PitchYawComponent) { 212 auto pitchYaw = cameraEntity.getFromComponent!PitchYawComponent(c => c.pitchYawVector); 213 return createFirstPersonViewMatrix(position, pitchYaw.x, pitchYaw.y); 214 } 215 216 auto orientation = cameraEntity.getFromComponent!OrientationR3Component(c => c.orientation, QuaternionD.createRotation(0, standardUpVector.vector)); 217 return position.toTranslationMatrix() * orientation.toRotationMatrix(); 218 } 219 220 private void useShaderProgram(OpenGlShaderProgram program) { 221 foreach(uniformBlock; program.uniformBlocks) { 222 if (uniformBlock is null) { 223 continue; 224 } 225 226 try { 227 sharedUniformBlockBuilder.buildData(uniformBlock, program); 228 } catch (UniformBlockBuildException e) { 229 log.error(e.msg); 230 } 231 232 program.bindUniformBlock(uniformBlock); 233 } 234 235 program.use(); 236 } 237 238 public override void cleanup() { 239 viewport.cleanup(); 240 } 241 242 public override bool acceptsEntity(Entity entity) { 243 if (entity.hasComponent!RenderableModelComponent && entity.hasComponent!ModelComponent) { 244 auto model = cast(OpenGlModel) entity.getFromComponent!ModelComponent(c => c.model); 245 return model !is null; 246 } 247 248 return entity.hasComponent!CameraComponent; 249 } 250 251 private void updateProjectionMatrix() { 252 auto viewportDimensions = viewport.dimensions; 253 double aspectRatio = cast(double) viewportDimensions.width / cast(double) viewportDimensions.height; 254 //TODO: Make fov configurable 255 projectionMatrix = createPerspectiveMatrix(45, aspectRatio, 0.1, 1_000); 256 } 257 258 } 259 260 class OpenGlTexture : Texture { 261 private RectangleU dimensions; 262 private GLuint textureObject; 263 private ubyte[] texelData; 264 private bool loadedIntoVram = false; 265 private bool generateMipMaps; 266 private string textureName; 267 268 this(ubyte[] texelData, RectangleU dimensions, bool generateMipMaps = true, string textureName = "") { 269 enforce!Exception(texelData.length % 4 == 0, "textelData should be aligned to 4-byte values (32bit). Does it contain the proper data format?"); 270 this.texelData = texelData; 271 this.dimensions = dimensions; 272 this.generateMipMaps = generateMipMaps; 273 this.textureName = textureName; 274 } 275 276 public void loadIntoVram() { 277 if (loadedIntoVram) return; 278 279 glCreateTextures(GL_TEXTURE_2D, 1, &textureObject); 280 glBindTexture(GL_TEXTURE_2D, textureObject); 281 glTextureStorage2D(textureObject, 1, GL_RGBA8, dimensions.width, dimensions.height); 282 glTextureSubImage2D(textureObject, 0, dimensions.x, dimensions.y, dimensions.width, dimensions.height, GL_RGBA, GL_UNSIGNED_BYTE, texelData.ptr); 283 284 if (generateMipMaps) { 285 glGenerateTextureMipmap(textureObject); 286 } 287 288 texelData = []; 289 loadedIntoVram = true; 290 } 291 292 public bool isLoadedIntoVram() { 293 return loadedIntoVram; 294 } 295 296 public void applyTexture() { 297 glBindTexture(GL_TEXTURE_2D, textureObject); 298 } 299 300 public override RectangleU getTextureSize() { 301 return dimensions; 302 } 303 304 public override string getName() { 305 return textureName; 306 } 307 } 308 309 }