1 /** 2 * Copyright: Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved 3 * Authors: $(Gabriel Gheorghe) 4 * License: $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007) 5 * Source: $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/graphics/opengl/backend.d, _backend.d) 6 * Documentation: 7 * Coverage: 8 */ 9 // TODO: Optimize imports. 10 // TODO: Get rid of public import. 11 // TODO: Extensions string[] with defaults. 12 module liberty.graphics.opengl.backend; 13 version (__OpenGL__) : 14 import core.stdc.stdlib; 15 import derelict.sdl2.sdl : SDL_GL_SwapWindow; 16 import std.string, std.conv, std.array, std.algorithm; 17 public import derelict.opengl.types: GLVersion; 18 import derelict.opengl; 19 import derelict.util.exception : ShouldThrow; 20 import derelict.opengl.gl; 21 import liberty.graphics.renderer : Vendor; 22 import liberty.graphics.video.backend : VideoBackend; 23 import liberty.core.engine; 24 /// The one exception type thrown in this wrapper. 25 /// A failing OpenGL function should <b>always</b> throw an $(D GLException). 26 class GLException : Exception { 27 /// 28 this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) pure nothrow @safe { 29 super(message, file, line, next); 30 } 31 } 32 /// 33 final class GLBackend : VideoBackend { 34 /// Load OpenGL library. 35 /// Throws: $(D GLException) on error. 36 this() @trusted { 37 ShouldThrow missingSymFunc(string symName) { 38 if (symName == "glGetSubroutineUniformLocation") { 39 return ShouldThrow.No; 40 } 41 if (symName == "glVertexAttribL1d") { 42 return ShouldThrow.No; 43 } 44 return ShouldThrow.Yes; 45 } 46 DerelictGL3.missingSymbolCallback = &missingSymFunc; 47 DerelictGL3.load(); 48 getLimits(false); 49 } 50 /// Returns true if the OpenGL extension is supported. 51 override bool supportsExtension(string extension) pure nothrow @safe @nogc { 52 foreach (el; _extensions) { 53 if (el == extension) { 54 return true; 55 } 56 } 57 return false; 58 } 59 /// Reload OpenGL function pointers. 60 /// Once a first OpenGL context has been created, you should call reload() to get the context you want. 61 /// This will attempt to load every OpenGL function except deprecated. 62 /// Warning: This may be dangerous because drivers may miss some functions! 63 override void reload() @trusted { 64 DerelictGL3.reload(); 65 getLimits(true); 66 } 67 /// Check for pending OpenGL errors, log a message if there is. 68 /// Only for debug purpose since this check will be disabled in a release build. 69 debug override void debugCheck() nothrow @trusted { 70 GLint er = glGetError(); 71 if (er != GL_NO_ERROR) { 72 flushGLErrors(); 73 assert(false, "OpenGL error: " ~ errorString(er)); 74 } 75 } 76 /// Checks pending OpenGL errors. 77 /// Throws: $(D GLException) if at least one OpenGL error was pending. 78 override void runtimeCheck() @trusted { 79 GLint er = glGetError(); 80 if (er != GL_NO_ERROR) { 81 string errorString = errorString(er); 82 flushGLErrors(); 83 throw new GLException(errorString); 84 } 85 } 86 /// Checks pending OpenGL errors. 87 /// Returns true if at least one OpenGL error was pending. 88 /// OpenGL error status is cleared. 89 override bool runtimeCheckNothrow() nothrow { 90 GLint r = glGetError(); 91 if (r != GL_NO_ERROR) { 92 flushGLErrors(); 93 return false; 94 } 95 return true; 96 } 97 /// Returns OpenGL string returned by $(D glGetString). 98 const(char)[] getString(GLenum name) @trusted { 99 const(char)* sZ = glGetString(name); 100 runtimeCheck(); 101 if (sZ is null) { 102 return "(unknown)"; 103 } else { 104 return sZ.fromStringz; 105 } 106 } 107 /// Returns OpenGL string returned by $(D glGetStringi). 108 const(char)[] getString(GLenum name, GLuint index) @trusted { 109 const(char)* sZ = glGetStringi(name, index); 110 runtimeCheck(); 111 if (sZ is null) { 112 return "(unknown)"; 113 } else { 114 return sZ.fromStringz; 115 } 116 } 117 /// Returns OpenGL major version. 118 override int majorVersion() pure nothrow const @safe @nogc @property { 119 return _majorVersion; 120 } 121 /// Returns OpenGL minor version. 122 override int minorVersion() pure nothrow const @safe @nogc @property { 123 return _minorVersion; 124 } 125 /// Returns OpenGL version string. 126 override const(char)[] versionString() @safe @property { 127 return getString(GL_VERSION); 128 } 129 /// Returns the company responsible for this OpenGL implementation. 130 override const(char)[] vendorString() @safe @property { 131 return getString(GL_VENDOR); 132 } 133 /// Tries to detect the driver maker. Returns identified vendor. 134 override Vendor vendor() @safe @property { 135 const(char)[] s = vendorString(); 136 if (canFind(s, "AMD") || canFind(s, "ATI") || canFind(s, "Advanced Micro Devices")) { 137 return Vendor.Amd; 138 } else if (canFind(s, "NVIDIA") || canFind(s, "nouveau") || canFind(s, "Nouveau")) { 139 return Vendor.Nvidia; 140 } else if (canFind(s, "Intel")) { 141 return Vendor.Intel; 142 } else if (canFind(s, "Mesa")) { 143 return Vendor.Mesa; 144 } else if (canFind(s, "Microsoft")) { 145 return Vendor.Microsoft; 146 } else if (canFind(s, "Apple")) { 147 return Vendor.Apple; 148 } else { 149 return Vendor.Other; 150 } 151 } 152 /// Returns the name of the renderer. 153 /// This name is typically specific to a particular configuration of a hardware platform. 154 override const(char)[] graphicsEngineString() @safe @property { 155 return getString(GL_RENDERER); 156 } 157 /// Returns GLSL version string. 158 override const(char)[] glslVersionString() @safe @property { 159 return getString(GL_SHADING_LANGUAGE_VERSION); 160 } 161 /// Returns a slice made up of available extension names. 162 override string[] extensions() pure nothrow @safe @nogc @property { 163 return _extensions; 164 } 165 /// Returns the requested int returned by $(D glGetFloatv). 166 /// Throws: $(D GLException) if at least one OpenGL error was pending. 167 int getInt(GLenum pname) @trusted { 168 GLint param; 169 glGetIntegerv(pname, ¶m); 170 runtimeCheck(); 171 return param; 172 } 173 /// Returns the requested float returned by $(D glGetFloatv). 174 /// Throws: $(D GLException) if at least one OpenGL error was pending. 175 float getFloat(GLenum pname) @trusted { 176 GLfloat res; 177 glGetFloatv(pname, &res); 178 runtimeCheck(); 179 return res; 180 } 181 /// Returns the maximum number of color attachments. This is the number of targets a fragment shader can output to. 182 /// You can rely on this number being at least 4 if MRT is supported. 183 override int maxColorAttachments() pure nothrow const @safe @nogc @property { 184 return _maxColorAttachments; 185 } 186 187 /// Sets the "active texture" which is more precisely active texture unit. 188 /// Throws: $(D GLException) on error. 189 override void activeTexture(int texture_id) @trusted @property { 190 glActiveTexture(GL_TEXTURE0 + texture_id); 191 runtimeCheck(); 192 } 193 /// 194 override void resizeViewport() @trusted { 195 glViewport(0, 0, CoreEngine.get.mainWindow.size.x, CoreEngine.get.mainWindow.size.y); 196 } 197 /// 198 override void clear() @trusted { 199 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 200 } 201 /// 202 override void clearColor(float r, float g, float b, float a) @trusted { 203 glClearColor(r, g, b, a); 204 } 205 /// Swap OpenGL buffers. 206 /// Throws: $(D GLException) on error. 207 override void swapBuffers() @trusted { 208 if (CoreEngine.get.mainWindow.glContext is null) { // TODO 209 throw new GLException("swapBuffers() failed: not an OpenGL window"); 210 } 211 SDL_GL_SwapWindow(CoreEngine.get.mainWindow._window); // TODO 212 } 213 package static string errorString(GLint er) pure nothrow @safe @nogc { 214 switch(er) { 215 case GL_NO_ERROR: 216 return "GL_NO_ERROR"; 217 case GL_INVALID_ENUM: 218 return "GL_INVALID_ENUM"; 219 case GL_INVALID_VALUE: 220 return "GL_INVALID_VALUE"; 221 case GL_INVALID_OPERATION: 222 return "GL_INVALID_OPERATION"; 223 case GL_OUT_OF_MEMORY: 224 return "GL_OUT_OF_MEMORY"; 225 default: 226 return "Unknown OpenGL error"; 227 } 228 } 229 private void getLimits(bool isReload) @safe { 230 if (isReload) { 231 const(char)[] verString = versionString; 232 int firstSpace = cast(int)countUntil(verString, " "); 233 if (firstSpace != -1) { 234 verString = verString[0..firstSpace]; 235 } 236 const(char)[][] verParts = std.array.split(verString, "."); 237 if (verParts.length < 2) { 238 cant_parse: 239 _majorVersion = 1; 240 _minorVersion = 1; 241 } else { 242 try { 243 _majorVersion = to!int(verParts[0]); 244 } catch (Exception e) { 245 goto cant_parse; 246 } 247 try { 248 _minorVersion = to!int(verParts[1]); 249 } catch (Exception e) { 250 goto cant_parse; 251 } 252 } 253 if (_majorVersion < 3) { 254 _extensions = std.array.split(getString(GL_EXTENSIONS).idup); 255 } else { 256 int numExtensions = getInt(GL_NUM_EXTENSIONS); 257 _extensions.length = 0; 258 for (int i = 0; i < numExtensions; ++i) { 259 _extensions ~= getString(GL_EXTENSIONS, i).idup; 260 } 261 } 262 _maxColorAttachments = getInt(GL_MAX_COLOR_ATTACHMENTS); 263 } else { 264 _majorVersion = 1; 265 _minorVersion = 1; 266 _extensions = []; 267 _maxColorAttachments = 0; 268 } 269 } 270 private void flushGLErrors() nothrow @trusted @nogc { 271 int timeout = 0; 272 while (++timeout <= 5) { 273 GLint r = glGetError(); 274 if (r == GL_NO_ERROR) { 275 break; 276 } 277 } 278 } 279 }