1 /** 2 Copyright: Copyright (c) 2015-2017 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko, Stephan Dilly (imgui_d_test). 5 */ 6 module voxelman.imgui_glfw; 7 8 import derelict.imgui.imgui; 9 import derelict.opengl3.gl3; 10 import derelict.glfw3.glfw3; 11 12 struct ImguiState 13 { 14 GLFWwindow* window; 15 double time = 0.0f; 16 bool[7] mousePressed; 17 float mouseWheel = 0.0f; 18 GLuint fontTexture = 0; 19 int shaderHandle = 0; 20 int vertHandle = 0; 21 int fragHandle = 0; 22 int attribLocationTex = 0; 23 int attribLocationProjMtx = 0; 24 int attribLocationPosition = 0; 25 int attribLocationUV = 0; 26 int attribLocationColor = 0; 27 uint vboHandle; 28 uint vaoHandle; 29 uint elementsHandle; 30 ClipboardHelper clipboardHelper; 31 32 void init(GLFWwindow* window, string[] fonts = null) 33 { 34 this.window = window; 35 clipboardHelper.window = window; 36 37 ImGuiIO* io = igGetIO(); 38 39 io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; 40 io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; 41 io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; 42 io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; 43 io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; 44 io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; 45 io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; 46 io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; 47 io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; 48 io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; 49 io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; 50 io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; 51 io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; 52 io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; 53 io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; 54 io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; 55 io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; 56 57 io.SetClipboardTextFn = &clipboardHelper.setClipboardText; 58 io.GetClipboardTextFn = &clipboardHelper.getClipboardText; 59 } 60 61 void newFrame() 62 { 63 if (!fontTexture) 64 createDeviceObjects(); 65 66 auto io = igGetIO(); 67 68 // Setup display size (every frame to accommodate for window resizing) 69 int w, h; 70 int display_w, display_h; 71 glfwGetWindowSize(window, &w, &h); 72 glfwGetFramebufferSize(window, &display_w, &display_h); 73 io.DisplaySize = ImVec2(cast(float)display_w, cast(float)display_h); 74 75 // Setup time step 76 double current_time = glfwGetTime(); 77 io.DeltaTime = time > 0.0 ? cast(float)(current_time - time) : cast(float)(1.0f/60.0f); 78 time = current_time; 79 80 // Setup inputs 81 // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) 82 if (glfwGetWindowAttrib(window, GLFW_FOCUSED)) 83 { 84 double mouse_x, mouse_y; 85 glfwGetCursorPos(window, &mouse_x, &mouse_y); 86 mouse_x *= cast(float)display_w / w; // Convert mouse coordinates to pixels 87 mouse_y *= cast(float)display_h / h; 88 io.MousePos = ImVec2(mouse_x, mouse_y); // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) 89 90 io.KeyShift = glfwGetKey(window, GLFW_KEY_LSHIFT) || glfwGetKey(window, GLFW_KEY_RSHIFT); 91 io.KeyCtrl = glfwGetKey(window, GLFW_KEY_LCTRL) || glfwGetKey(window, GLFW_KEY_RCTRL); 92 io.KeyAlt = glfwGetKey(window, GLFW_KEY_LALT) || glfwGetKey(window, GLFW_KEY_RALT); 93 } 94 else 95 { 96 io.MousePos = ImVec2(-1,-1); 97 } 98 99 for (int i = 0; i < 3; i++) 100 { 101 io.MouseDown[i] = mousePressed[i] || glfwGetMouseButton(window, i) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. 102 mousePressed[i] = false; 103 } 104 105 io.MouseWheel = mouseWheel; 106 mouseWheel = 0.0f; 107 108 // Hide/show hardware mouse cursor 109 //glfwSetInputMode(window, GLFW_CURSOR, io.MouseDrawCursor ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_NORMAL); 110 111 igNewFrame(); 112 } 113 114 bool mouseCaptured() @property 115 { 116 return igGetIO().WantCaptureMouse; 117 } 118 119 bool keyboardCaptured() @property 120 { 121 return igGetIO().WantCaptureKeyboard || igGetIO().WantTextInput; 122 } 123 124 void render() 125 { 126 igRender(); 127 renderDrawLists(igGetDrawData()); 128 } 129 130 void renderDrawLists(ImDrawData* data) 131 { 132 // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled 133 GLint last_program, last_texture; 134 glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); 135 glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 136 glEnable(GL_BLEND); 137 glBlendEquation(GL_FUNC_ADD); 138 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 139 glDisable(GL_CULL_FACE); 140 glDisable(GL_DEPTH_TEST); 141 glEnable(GL_SCISSOR_TEST); 142 glActiveTexture(GL_TEXTURE0); 143 144 auto io = igGetIO(); 145 // Setup orthographic projection matrix 146 const float width = io.DisplaySize.x; 147 const float height = io.DisplaySize.y; 148 const float[4][4] ortho_projection = 149 [ 150 [ 2.0f/width, 0.0f, 0.0f, 0.0f ], 151 [ 0.0f, 2.0f/-height, 0.0f, 0.0f ], 152 [ 0.0f, 0.0f, -1.0f, 0.0f ], 153 [ -1.0f, 1.0f, 0.0f, 1.0f ], 154 ]; 155 glUseProgram(shaderHandle); 156 glUniform1i(attribLocationTex, 0); 157 glUniformMatrix4fv(attribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); 158 159 glBindVertexArray(vaoHandle); 160 glBindBuffer(GL_ARRAY_BUFFER, vboHandle); 161 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementsHandle); 162 163 foreach (n; 0..data.CmdListsCount) 164 { 165 ImDrawList* cmd_list = data.CmdLists[n]; 166 ImDrawIdx* idx_buffer_offset; 167 168 auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list); 169 auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list); 170 171 glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW); 172 glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW); 173 174 auto cmdCnt = ImDrawList_GetCmdSize(cmd_list); 175 176 foreach(i; 0..cmdCnt) 177 { 178 auto pcmd = ImDrawList_GetCmdPtr(cmd_list, i); 179 180 if (pcmd.UserCallback) 181 { 182 pcmd.UserCallback(cmd_list, pcmd); 183 } 184 else 185 { 186 glBindTexture(GL_TEXTURE_2D, cast(GLuint)pcmd.TextureId); 187 glScissor(cast(int)pcmd.ClipRect.x, cast(int)(height - pcmd.ClipRect.w), cast(int)(pcmd.ClipRect.z - pcmd.ClipRect.x), cast(int)(pcmd.ClipRect.w - pcmd.ClipRect.y)); 188 glDrawElements(GL_TRIANGLES, pcmd.ElemCount, GL_UNSIGNED_SHORT, idx_buffer_offset); 189 } 190 191 idx_buffer_offset += pcmd.ElemCount; 192 } 193 } 194 195 // Restore modified state 196 glBindVertexArray(0); 197 glBindBuffer(GL_ARRAY_BUFFER, 0); 198 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 199 glUseProgram(last_program); 200 glDisable(GL_SCISSOR_TEST); 201 glBindTexture(GL_TEXTURE_2D, last_texture); 202 } 203 204 void createDeviceObjects() 205 { 206 const GLchar* vertex_shader = 207 "#version 330\n"~ 208 "uniform mat4 ProjMtx;\n"~ 209 "in vec2 Position;\n"~ 210 "in vec2 UV;\n"~ 211 "in vec4 Color;\n"~ 212 "out vec2 Frag_UV;\n"~ 213 "out vec4 Frag_Color;\n"~ 214 "void main()\n"~ 215 "{\n"~ 216 " Frag_UV = UV;\n"~ 217 " Frag_Color = Color;\n"~ 218 " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"~ 219 "}\n"; 220 221 const GLchar* fragment_shader = 222 "#version 330\n"~ 223 "uniform sampler2D Texture;\n"~ 224 "in vec2 Frag_UV;\n"~ 225 "in vec4 Frag_Color;\n"~ 226 "out vec4 Out_Color;\n"~ 227 "void main()\n"~ 228 "{\n"~ 229 " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n"~ 230 "}\n"; 231 232 shaderHandle = glCreateProgram(); 233 vertHandle = glCreateShader(GL_VERTEX_SHADER); 234 fragHandle = glCreateShader(GL_FRAGMENT_SHADER); 235 glShaderSource(vertHandle, 1, &vertex_shader, null); 236 glShaderSource(fragHandle, 1, &fragment_shader, null); 237 glCompileShader(vertHandle); 238 glCompileShader(fragHandle); 239 glAttachShader(shaderHandle, vertHandle); 240 glAttachShader(shaderHandle, fragHandle); 241 glLinkProgram(shaderHandle); 242 243 attribLocationTex = glGetUniformLocation(shaderHandle, "Texture"); 244 attribLocationProjMtx = glGetUniformLocation(shaderHandle, "ProjMtx"); 245 attribLocationPosition = glGetAttribLocation(shaderHandle, "Position"); 246 attribLocationUV = glGetAttribLocation(shaderHandle, "UV"); 247 attribLocationColor = glGetAttribLocation(shaderHandle, "Color"); 248 249 glGenBuffers(1, &vboHandle); 250 glGenBuffers(1, &elementsHandle); 251 252 glGenVertexArrays(1, &vaoHandle); 253 glBindVertexArray(vaoHandle); 254 glBindBuffer(GL_ARRAY_BUFFER, vboHandle); 255 glEnableVertexAttribArray(attribLocationPosition); 256 glEnableVertexAttribArray(attribLocationUV); 257 glEnableVertexAttribArray(attribLocationColor); 258 259 glVertexAttribPointer(attribLocationPosition, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)0); 260 glVertexAttribPointer(attribLocationUV, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof); 261 glVertexAttribPointer(attribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof); 262 263 glBindVertexArray(0); 264 glBindBuffer(GL_ARRAY_BUFFER, 0); 265 266 createFontsTexture(); 267 } 268 269 void createFontsTexture() 270 { 271 ImGuiIO* io = igGetIO(); 272 273 ubyte* pixels; 274 int width, height; 275 ImFontAtlas_GetTexDataAsRGBA32(io.Fonts,&pixels,&width,&height,null); 276 277 glGenTextures(1, &fontTexture); 278 glBindTexture(GL_TEXTURE_2D, fontTexture); 279 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 280 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 281 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 282 283 // Store our identifier 284 ImFontAtlas_SetTexID(io.Fonts, cast(void*)fontTexture); 285 } 286 287 void onMousePressed(uint button) 288 { 289 mousePressed[button] = true; 290 } 291 292 void onMouseReleased(uint button) 293 { 294 mousePressed[button] = false; 295 } 296 297 void scrollCallback(float scroll) 298 { 299 mouseWheel += scroll; 300 } 301 302 void onKeyPressed(uint key) 303 { 304 igGetIO().KeysDown[key] = true; 305 if (key == GLFW_KEY_KP_ENTER) 306 igGetIO().KeysDown[GLFW_KEY_ENTER] = true; 307 } 308 309 void onKeyReleased(uint key) 310 { 311 igGetIO().KeysDown[key] = false; 312 if (key == GLFW_KEY_KP_ENTER) 313 igGetIO().KeysDown[GLFW_KEY_ENTER] = false; 314 } 315 316 void charCallback(dchar c) 317 { 318 if (c > 0 && c < 0x10000) 319 ImGuiIO_AddInputCharacter(cast(ushort)c); 320 } 321 322 void shutdown() 323 { 324 if (vaoHandle) glDeleteVertexArrays(1, &vaoHandle); 325 if (vboHandle) glDeleteBuffers(1, &vboHandle); 326 if (elementsHandle) glDeleteBuffers(1, &elementsHandle); 327 vaoHandle = 0; 328 vboHandle = 0; 329 elementsHandle = 0; 330 331 glDetachShader(shaderHandle, vertHandle); 332 glDeleteShader(vertHandle); 333 vertHandle = 0; 334 335 glDetachShader(shaderHandle, fragHandle); 336 glDeleteShader(fragHandle); 337 fragHandle = 0; 338 339 glDeleteProgram(shaderHandle); 340 shaderHandle = 0; 341 342 if (fontTexture) 343 { 344 glDeleteTextures(1, &fontTexture); 345 ImFontAtlas_SetTexID(igGetIO().Fonts, cast(void*)0); 346 fontTexture = 0; 347 } 348 349 igShutdown(); 350 } 351 } 352 353 GLFWwindow* startGlfw(string windowTitle, int w, int h) 354 { 355 // Setup window 356 glfwSetErrorCallback(&error_callback); 357 if (!glfwInit()) 358 return null; 359 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 360 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); 361 //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 362 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); 363 glfwWindowHint(GLFW_VISIBLE, false); 364 auto window = glfwCreateWindow(w, h, windowTitle.ptr, null, null); 365 glfwMakeContextCurrent(window); 366 367 DerelictGL3.reload(); 368 369 return window; 370 } 371 372 extern(C) nothrow void error_callback(int error, const(char)* description) 373 { 374 import std.stdio; 375 import std.conv; 376 try writefln("glfw err: %s ('%s')", error, to!string(description)); 377 catch(Throwable){} 378 } 379 380 struct ClipboardHelper 381 { 382 static GLFWwindow* window; 383 384 static extern(C) nothrow 385 const(char)* getClipboardText() 386 { 387 return glfwGetClipboardString(window); 388 } 389 390 static extern(C) nothrow 391 void setClipboardText(const(char)* text) 392 { 393 glfwSetClipboardString(window, text); 394 } 395 }