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 }