1 /*
2  *  BSD 3-Clause License
3  *  
4  *  Copyright (c) 2016, Mango-Engine Team
5  *  All rights reserved.
6  *  
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions are met:
9  *  
10  *  * Redistributions of source code must retain the above copyright notice, this
11  *    list of conditions and the following disclaimer.
12  *  
13  *  * Redistributions in binary form must reproduce the above copyright notice,
14  *    this list of conditions and the following disclaimer in the documentation
15  *    and/or other materials provided with the distribution.
16  *  
17  *  * Neither the name of the copyright holder nor the names of its
18  *    contributors may be used to endorse or promote products derived from
19  *    this software without specific prior written permission.
20  *  
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25  *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 module mango_engine.graphics.opengl.gl_window;
33 
34 import mango_engine.graphics.opengl.gl_backend;
35 import mango_engine.graphics.window;
36 import mango_engine.logging;
37 import mango_engine.game;
38 import mango_engine.event.core;
39 import mango_engine.event.input;
40 
41 import blocksound.util : toCString, toDString;
42 
43 import derelict.glfw3;
44 import derelict.opengl3.gl3 : glGetString, GL_VERSION, GL_RENDERER, GL_VENDOR;
45 
46 import std.conv;
47 
48 package __gshared GLWindow[GLFWwindow*] windows;
49 
50 extern(C) void glwindow_glfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) @system nothrow {
51     windows[window].onKey(key, scancode);
52 }
53 
54 class GLWindow : Window {
55     private GLFWwindow* window;
56     private shared size_t keyEventCounter;
57     private shared KeyPressEvent[size_t] keyEvents;
58 
59     this(in string title, in uint width, in uint height, SyncType syncType) @safe {
60         super(title, width, height, syncType);
61         gl_check();
62 
63         createWindow();
64     }
65 
66     private void createWindow() @trusted {
67         // Set OpenGL Information
68         glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
69         glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, MANGO_GL_VERSION_MAJOR);
70         glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, MANGO_GL_VERSION_MINOR);
71 
72         window = glfwCreateWindow(width, height, toCString(title), null, null);
73         if(!window) {
74             if(failedContext) { // From gl_backend
75                 throw new WindowContextFailedException("Context Failed! Does the host machine support OpenGL " ~ MANGO_GL_VERSION_STRING ~ "?");
76             } else 
77                 throw new Exception("Failed to create window!");
78         }
79 
80         glfwMakeContextCurrent(window); // Set our main OpenGL context
81 
82         GLBackend.loadCoreMethods(); // Load the non-deprecated methods (core)
83 
84         string glVersion = toDString(glGetString(GL_VERSION));
85         // backendLogger: from gl_backend.d
86         (cast(Logger) backendLogger).logInfo("GL_VERSION: " ~ glVersion);
87         (cast(Logger) backendLogger).logInfo("GL_RENDERER: " ~ toDString(glGetString(GL_RENDERER)));
88         (cast(Logger) backendLogger).logInfo("GL_VENDOR: " ~ toDString(glGetString(GL_VENDOR)));
89 
90         glfwSetKeyCallback(window, &glwindow_glfwKeyCallback);
91     }
92 
93     package void onKey(int key, int scancode) @system nothrow {
94         import core.atomic;
95         keyEvents[atomicOp!"+="(keyEventCounter, 1)] = cast(shared) new KeyPressEvent(key, scancode);
96     }
97 
98     private void evtHook_processInput(Event e) @system {
99         glfwPollEvents();
100 
101         size_t[] toRemove;
102         foreach(key, event; keyEvents) {
103             game.eventManager.fireEvent(cast(KeyPressEvent) event);
104         }
105 
106         foreach(t; toRemove)
107             keyEvents.remove(t);
108 
109         if(glfwWindowShouldClose(window)) {
110             game.stop();
111         }
112     }
113     
114     override {
115         protected void setGame_() @system {
116             game.eventManager.registerEventHook(TickEvent.classinfo.name,
117                 EventHook(&this.evtHook_processInput, false)
118             );
119         }
120 
121         shared void updateBuffers() @system {
122             glfwSwapBuffers(cast(GLFWwindow*) window);
123         }
124 
125         protected void setSync_(in SyncType syncType) @system {
126             final switch(syncType) {
127                 case SyncType.SYNC_NONE:
128                     glfwSwapInterval(0);
129                     break;
130                 case SyncType.SYNC_VSYNC:
131                     glfwSwapInterval(1);
132                     break;
133                 case SyncType.SYNC_ADAPTIVE:
134                     throw new Exception("Adaptive Sync not implemented!");
135             }
136         }
137 
138         protected void setTitle_(in string title) @system {
139             glfwSetWindowTitle(window, toCString(title));
140         }
141 
142         protected void resize_(in uint width, in uint height) @system {
143             glfwSetWindowSize(window, width, height);
144         }
145     }
146 }