1 /*
2 Copyright (c) 2017-2018 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 core.stdc.stdlib;
39 
40 import dlib.core.memory;
41 import dagon.core.libs;
42 import dagon.core.event;
43 
44 void exitWithError(string message)
45 {
46     writeln(message);
47     core.stdc.stdlib.exit(1);
48 }
49 
50 enum DagonEvent
51 {
52     Exit = -1
53 }
54 
55 enum string[GLenum] GLErrorStrings = [
56     GL_NO_ERROR: "GL_NO_ERROR",
57     GL_INVALID_ENUM: "GL_INVALID_ENUM",
58     GL_INVALID_VALUE: "GL_INVALID_VALUE",
59     GL_INVALID_OPERATION: "GL_INVALID_OPERATION",
60     GL_INVALID_FRAMEBUFFER_OPERATION: "GL_INVALID_FRAMEBUFFER_OPERATION",
61     GL_OUT_OF_MEMORY: "GL_OUT_OF_MEMORY"
62 ];
63 
64 extern(System) nothrow void messageCallback(
65     GLenum source,
66     GLenum type,
67     GLuint id,
68     GLenum severity,
69     GLsizei length,
70     const GLchar* message,
71     const GLvoid* userParam)
72 {
73     string msg = "%stype = 0x%x, severity = 0x%x, message = %s\n";
74     string err = "OpenGL error: ";
75     string empty = "";
76     if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
77         printf(msg.ptr, (type == GL_DEBUG_TYPE_ERROR ? err.ptr : empty.ptr), type, severity, message);
78 }
79 
80 /++
81     Base class to inherit Dagon applications from.
82     `Application` wraps SDL2 window, loads dynamic link libraries using Derelict,
83     is responsible for initializing OpenGL context and doing main game loop.
84 +/
85 class Application: EventListener
86 {
87     uint width;
88     uint height;
89     SDL_Window* window = null;
90     SDL_GLContext glcontext;
91     string libdir;
92     
93     private EventManager _eventManager;
94 
95     /++
96         Constructor.
97         * `winWidth` - window width
98         * `winHeight` - window height
99         * `fullscreen` - if true, the application will run in fullscreen mode
100         * `windowTitle` - window title
101         * `args` - command line arguments
102     +/
103     this(uint winWidth, uint winHeight, bool fullscreen, string windowTitle, string[] args)
104     {
105         version(NoFreetype)
106         {
107         }
108         else
109         {
110             FreetypeSupport ftsup = loadFreetype();
111             if (ftsup != freetypeSupport)
112             {
113                 if (ftsup == FreetypeSupport.badLibrary)
114                     writeln("Warning: failed to load some Freetype functions. It seems that you have an old version of Freetype. Dagon will try to use it, but it is recommended to install Freetype 2.8.1 or higher");
115                 else
116                     exitWithError("Error: Freetype library is not found. Please, install Freetype 2.8.1");
117             }
118         }
119 
120         version(NoNuklear)
121         {
122         }
123         else
124         {
125             NuklearSupport nuksup = loadNuklear();
126             if (nuksup != NuklearSupport.Nuklear4)
127             {
128                 exitWithError("Error: Nuklear library is not found. Please, install Nuklear.");
129             }
130         }
131 
132         SDLSupport sdlsup = loadSDL();
133         if (sdlsup != sdlSupport)
134         {
135             if (sdlsup == SDLSupport.badLibrary)
136                 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.5 or higher");
137             else
138                 exitWithError("Error: SDL library is not found. Please, install SDL 2.0.5");
139         }
140 
141         if (SDL_Init(SDL_INIT_EVERYTHING) == -1)
142             exitWithError("Error: failed to init SDL: " ~ to!string(SDL_GetError()));
143 
144         width = winWidth;
145         height = winHeight;
146 
147         SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
148 
149         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
150         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
151         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
152         SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
153         SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
154         SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
155         SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
156 
157         window = SDL_CreateWindow(toStringz(windowTitle),
158             SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
159         if (window is null)
160             exitWithError("Error: failed to create window: " ~ to!string(SDL_GetError()));
161 
162         SDL_GL_SetSwapInterval(1);
163 
164         glcontext = SDL_GL_CreateContext(window);
165         if (glcontext is null)
166             exitWithError("Error: failed to create OpenGL context: " ~ to!string(SDL_GetError()));
167 
168         SDL_GL_MakeCurrent(window, glcontext);
169 
170         GLSupport glsup = loadOpenGL();
171         if (isOpenGLLoaded())
172         {
173             if (glsup < GLSupport.gl40)
174             {
175                 exitWithError("Error: Dagon requires OpenGL 4.0, but it seems that your graphics card does not support it");
176             }
177         }
178         else
179         {
180             exitWithError("Error: failed to load OpenGL functions. Please, update graphics card driver and make sure it supports OpenGL 4.0");
181         }
182 
183         if (fullscreen)
184             SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
185 
186         _eventManager = New!EventManager(window, width, height);
187         super(_eventManager, null);
188 
189         // Initialize OpenGL
190         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
191         glClearDepth(1.0);
192         glDepthFunc(GL_LESS);
193         glEnable(GL_DEPTH_TEST);
194         glEnable(GL_POLYGON_OFFSET_FILL);
195         glCullFace(GL_BACK);
196         glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
197         
198         glClear(GL_COLOR_BUFFER_BIT);
199         SDL_GL_SwapWindow(window);
200         
201         debug
202         {
203             if (hasKHRDebug)
204             {
205                 glEnable(GL_DEBUG_OUTPUT);
206                 glDebugMessageCallback(&messageCallback, null);
207             }
208             else
209             {
210                 writeln("GL_KHR_debug is not supported, debug output is not available");
211             }
212         }
213     }
214 
215     override void onUserEvent(int code)
216     {
217         if (code == DagonEvent.Exit)
218         {
219             exit();
220         }
221     }
222 
223     void onUpdate(double dt)
224     {
225         // Override me
226     }
227 
228     void onRender()
229     {
230         // Override me
231     }
232 
233     void checkGLError()
234     {
235         GLenum error = GL_NO_ERROR;
236         error = glGetError();
237         if (error != GL_NO_ERROR)
238         {
239             writefln("OpenGL error %s: %s", error, GLErrorStrings[error]);
240             //eventManager.running = false;
241         }
242     }
243 
244     void run()
245     {
246         while(eventManager.running)
247         {
248             beginRender();
249             onUpdate(eventManager.deltaTime);
250             onRender();
251             endRender();
252         }
253     }
254 
255     void beginRender()
256     {
257         eventManager.update();
258         processEvents();
259     }
260 
261     void endRender()
262     {
263         debug checkGLError();
264         SDL_GL_SwapWindow(window);
265     }
266 
267     void exit()
268     {
269         eventManager.exit();
270     }
271 
272     ~this()
273     {
274         SDL_GL_DeleteContext(glcontext);
275         SDL_DestroyWindow(window);
276         SDL_Quit();
277         Delete(_eventManager);
278     }
279 }