1 /*
2 Copyright (c) 2017-2022 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 /++
29     Base class to inherit Dagon applications from.
30 +/
31 module dagon.core.application;
32 
33 import std.stdio;
34 import std.conv;
35 import std.getopt;
36 import std.string;
37 import std.file;
38 import std.algorithm: canFind;
39 import core.stdc.stdlib;
40 
41 import dlib.core.memory;
42 import dagon.core.bindings;
43 import dagon.core.event;
44 import dagon.core.time;
45 
46 void exitWithError(string message = "")
47 {
48     if (message.length)
49         writeln(message);
50     core.stdc.stdlib.exit(1);
51 }
52 
53 enum DagonEvent
54 {
55     Exit = -1
56 }
57 
58 enum GL_FRAMEBUFFER_SRGB = 0x8DB9;
59 
60 enum string[GLenum] GLErrorStrings = [
61     GL_NO_ERROR: "GL_NO_ERROR",
62     GL_INVALID_ENUM: "GL_INVALID_ENUM",
63     GL_INVALID_VALUE: "GL_INVALID_VALUE",
64     GL_INVALID_OPERATION: "GL_INVALID_OPERATION",
65     GL_INVALID_FRAMEBUFFER_OPERATION: "GL_INVALID_FRAMEBUFFER_OPERATION",
66     GL_OUT_OF_MEMORY: "GL_OUT_OF_MEMORY"
67 ];
68 
69 enum string[GLenum] GLDebugSourceStrings = [
70     GL_DEBUG_SOURCE_API: "GL_DEBUG_SOURCE_API",
71     GL_DEBUG_SOURCE_WINDOW_SYSTEM: "GL_DEBUG_SOURCE_WINDOW_SYSTEM",
72     GL_DEBUG_SOURCE_SHADER_COMPILER: "GL_DEBUG_SOURCE_SHADER_COMPILER",
73     GL_DEBUG_SOURCE_THIRD_PARTY: "GL_DEBUG_SOURCE_THIRD_PARTY",
74     GL_DEBUG_SOURCE_APPLICATION: "GL_DEBUG_SOURCE_APPLICATION",
75     GL_DEBUG_SOURCE_OTHER: "GL_DEBUG_SOURCE_OTHER"
76 ];
77 
78 enum string[GLenum] GLDebugTypeStrings = [
79     GL_DEBUG_TYPE_ERROR: "GL_DEBUG_TYPE_ERROR",
80     GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: "GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR",
81     GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: "GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR",
82     GL_DEBUG_TYPE_PORTABILITY: "GL_DEBUG_TYPE_PORTABILITY",
83     GL_DEBUG_TYPE_PERFORMANCE: "GL_DEBUG_TYPE_PERFORMANCE",
84     GL_DEBUG_TYPE_MARKER: "GL_DEBUG_TYPE_MARKER",
85     GL_DEBUG_TYPE_PUSH_GROUP: "GL_DEBUG_TYPE_PUSH_GROUP",
86     GL_DEBUG_TYPE_POP_GROUP: "GL_DEBUG_TYPE_POP_GROUP",
87     GL_DEBUG_TYPE_OTHER: "GL_DEBUG_TYPE_OTHER"
88 ];
89 
90 enum string[GLenum] GLDebugSeverityStrings = [
91     GL_DEBUG_SEVERITY_HIGH: "GL_DEBUG_SEVERITY_HIGH",
92     GL_DEBUG_SEVERITY_MEDIUM: "GL_DEBUG_SEVERITY_MEDIUM",
93     GL_DEBUG_SEVERITY_LOW: "GL_DEBUG_SEVERITY_LOW",
94     GL_DEBUG_SEVERITY_NOTIFICATION: "GL_DEBUG_SEVERITY_NOTIFICATION"
95 ];
96 
97 extern(System) void messageCallback(
98     GLenum source,
99     GLenum type,
100     GLuint id,
101     GLenum severity,
102     GLsizei length,
103     const GLchar* message,
104     const GLvoid* userParam) nothrow
105 {
106     string msg = "%ssource = %s, type = %s, severity = %s, message = %s\n";
107     string err = "OpenGL error: ";
108     string empty = "";
109     if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
110     {
111         string sourceStr = GLDebugSourceStrings[source];
112         string typeStr = GLDebugTypeStrings[type];
113         string severityStr = GLDebugSeverityStrings[severity];
114         printf(msg.ptr, (type == GL_DEBUG_TYPE_ERROR ? err.ptr : empty.ptr), sourceStr.ptr, typeStr.ptr, severityStr.ptr, message);
115         if (severity == GL_DEBUG_SEVERITY_HIGH)
116             core.stdc.stdlib.exit(1);
117     }
118 }
119 
120 private
121 {
122     __gshared int[] compressedTextureFormats;
123 
124     void enumerateCompressedTextureFormats()
125     {
126         int numCompressedFormats = 0;
127         glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
128         if (numCompressedFormats)
129         {
130             compressedTextureFormats = New!(int[])(numCompressedFormats);
131             glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compressedTextureFormats.ptr);
132         }
133     }
134 
135     void releaseCompressedTextureFormats()
136     {
137         if (compressedTextureFormats.length)
138             Delete(compressedTextureFormats);
139     }
140 }
141 
142 bool compressedTextureFormatSupported(GLenum format)
143 {
144     if (compressedTextureFormats.length)
145         return compressedTextureFormats.canFind(format);
146     else
147         return false;
148 }
149 
150 /++
151     Base class to inherit Dagon applications from.
152     `Application` wraps SDL2 window, loads dynamic link libraries using BindBC,
153     is responsible for initializing OpenGL context and doing main game loop.
154 +/
155 class Application: EventListener
156 {
157     uint width;
158     uint height;
159     SDL_Window* window = null;
160     SDL_GLContext glcontext;
161     private EventManager _eventManager;
162     Cadencer cadencer;
163 
164     /++
165         Constructor.
166         * `winWidth` - window width
167         * `winHeight` - window height
168         * `fullscreen` - if true, the application will run in fullscreen mode
169         * `windowTitle` - window title
170         * `args` - command line arguments
171     +/
172     this(uint winWidth, uint winHeight, bool fullscreen, string windowTitle, string[] args)
173     {
174         SDLSupport sdlsup = loadSDL();
175         if (sdlsup != sdlSupport)
176         {
177             if (sdlsup == SDLSupport.badLibrary)
178                 writeln("Warning: failed to load some SDL functions. It seems that you have an old version of SDL. Dagon will try to use it, but it is recommended to install SDL 2.0.14 or higher");
179             else
180                 exitWithError("Error: SDL library is not found. Please, install SDL 2.0.14");
181         }
182 
183         if (SDL_Init(SDL_INIT_EVERYTHING) == -1)
184             exitWithError("Error: failed to init SDL: " ~ to!string(SDL_GetError()));
185 
186         width = winWidth;
187         height = winHeight;
188 
189         SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
190         SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
191         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
192         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
193         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
194         SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
195         SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
196         SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
197         SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
198         //SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
199 
200         window = SDL_CreateWindow(toStringz(windowTitle),
201             SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
202         if (window is null)
203             exitWithError("Error: failed to create window: " ~ to!string(SDL_GetError()));
204 
205         glcontext = SDL_GL_CreateContext(window);
206         if (glcontext is null)
207             exitWithError("Error: failed to create OpenGL context: " ~ to!string(SDL_GetError()));
208         SDL_GL_MakeCurrent(window, glcontext);
209         SDL_GL_SetSwapInterval(1);
210         
211         GLSupport glsup = loadOpenGL();
212         if (isOpenGLLoaded())
213         {
214             if (glsup < GLSupport.gl40)
215             {
216                 exitWithError("Error: Dagon requires OpenGL 4.0, but it seems that your graphics card does not support it");
217             }
218         }
219         else
220         {
221             exitWithError("Error: failed to load OpenGL functions. Please, update graphics card driver and make sure it supports OpenGL 4.0");
222         }
223 
224         if (fullscreen)
225             SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
226 
227         _eventManager = New!EventManager(window, width, height);
228         super(_eventManager, null);
229 
230         // Initialize OpenGL
231         //glEnable(GL_FRAMEBUFFER_SRGB);
232         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
233         glClearDepth(1.0);
234         glEnable(GL_SCISSOR_TEST);
235         glDepthFunc(GL_LESS);
236         glEnable(GL_DEPTH_TEST);
237         glEnable(GL_POLYGON_OFFSET_FILL);
238         glCullFace(GL_BACK);
239         glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
240 
241         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
242         SDL_GL_SwapWindow(window);
243 
244         enumerateCompressedTextureFormats();
245 
246         // Debug output
247         debug
248         {
249             if (hasKHRDebug)
250             {
251                 glEnable(GL_DEBUG_OUTPUT);
252                 glDebugMessageCallback(&messageCallback, null);
253             }
254             else
255             {
256                 writeln("GL_KHR_debug is not supported, debug output is not available");
257             }
258         }
259         
260         cadencer = New!Cadencer(&onAnimationFrame, 60, this);
261     }
262 
263     ~this()
264     {
265         releaseCompressedTextureFormats();
266 
267         SDL_GL_DeleteContext(glcontext);
268         SDL_DestroyWindow(window);
269         SDL_Quit();
270         Delete(_eventManager);
271     }
272 
273     void maximizeWindow()
274     {
275         SDL_MaximizeWindow(window);
276     }
277 
278     override void onUserEvent(int code)
279     {
280         if (code == DagonEvent.Exit)
281         {
282             exit();
283         }
284     }
285 
286     void onUpdate(Time t)
287     {
288         // Override me
289     }
290 
291     void onRender()
292     {
293         // Override me
294     }
295 
296     void onAnimationFrame(Time t)
297     {
298         eventManager.update();
299         processEvents();
300         onUpdate(t);
301         onRender();
302         debug checkGLError();
303         SDL_GL_SwapWindow(window);
304     }
305 
306     void checkGLError()
307     {
308         GLenum error = GL_NO_ERROR;
309         error = glGetError();
310         if (error != GL_NO_ERROR)
311         {
312             writefln("OpenGL error %s: %s", error, GLErrorStrings[error]);
313         }
314     }
315 
316     void run()
317     {
318         Time t = Time(0.0, 0.0);
319         while(eventManager.running)
320         {
321             eventManager.updateTimer();
322             t.delta = eventManager.deltaTime;
323             cadencer.update(t);
324             t.elapsed += t.delta;
325         }
326     }
327 
328     void exit()
329     {
330         eventManager.exit();
331     }
332 }