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