1 /** 2 Copyright: Copyright (c) 2015-2016 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[3] 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) 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 } 306 307 void onKeyReleased(uint key) 308 { 309 igGetIO().KeysDown[key] = false; 310 } 311 312 void charCallback(dchar c) 313 { 314 if (c > 0 && c < 0x10000) 315 ImGuiIO_AddInputCharacter(cast(ushort)c); 316 } 317 318 void shutdown() 319 { 320 if (vaoHandle) glDeleteVertexArrays(1, &vaoHandle); 321 if (vboHandle) glDeleteBuffers(1, &vboHandle); 322 if (elementsHandle) glDeleteBuffers(1, &elementsHandle); 323 vaoHandle = 0; 324 vboHandle = 0; 325 elementsHandle = 0; 326 327 glDetachShader(shaderHandle, vertHandle); 328 glDeleteShader(vertHandle); 329 vertHandle = 0; 330 331 glDetachShader(shaderHandle, fragHandle); 332 glDeleteShader(fragHandle); 333 fragHandle = 0; 334 335 glDeleteProgram(shaderHandle); 336 shaderHandle = 0; 337 338 if (fontTexture) 339 { 340 glDeleteTextures(1, &fontTexture); 341 ImFontAtlas_SetTexID(igGetIO().Fonts, cast(void*)0); 342 fontTexture = 0; 343 } 344 345 igShutdown(); 346 } 347 } 348 349 GLFWwindow* startGlfw(string windowTitle, int w, int h) 350 { 351 // Setup window 352 glfwSetErrorCallback(&error_callback); 353 if (!glfwInit()) 354 return null; 355 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 356 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); 357 //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 358 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); 359 glfwWindowHint(GLFW_VISIBLE, false); 360 auto window = glfwCreateWindow(w, h, windowTitle.ptr, null, null); 361 glfwMakeContextCurrent(window); 362 363 DerelictGL3.reload(); 364 365 return window; 366 } 367 368 extern(C) nothrow void error_callback(int error, const(char)* description) 369 { 370 import std.stdio; 371 import std.conv; 372 try writefln("glfw err: %s ('%s')", error, to!string(description)); 373 catch{} 374 } 375 376 struct ClipboardHelper 377 { 378 static GLFWwindow* window; 379 380 static extern(C) nothrow 381 const(char)* getClipboardText() 382 { 383 return glfwGetClipboardString(window); 384 } 385 386 static extern(C) nothrow 387 void setClipboardText(const(char)* text) 388 { 389 glfwSetClipboardString(window, text); 390 } 391 }