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, &param);
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 }