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.5 or higher"); 141 else 142 exitWithError("Error: SDL library is not found. Please, install SDL 2.0.5"); 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 }