1 module imgui_glfw; 2 3 import derelict.imgui.imgui; 4 import derelict.opengl3.gl3; 5 import derelict.glfw3.glfw3; 6 7 version(Windows) { 8 import core.sys.windows.windows : HWND, HGLRC; 9 mixin DerelictGLFW3_NativeBind; 10 } 11 12 // Data 13 GLFWwindow* g_Window = null; 14 double g_Time = 0.0f; 15 bool[3] g_MousePressed = [ false, false, false ]; 16 float g_MouseWheel = 0.0f; 17 GLuint g_FontTexture = 0; 18 int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; 19 int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; 20 int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; 21 uint g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0; 22 23 // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) 24 // If text or lines are blurry when integrating ImGui in your engine: 25 // - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) 26 extern(C) nothrow void igImplGlfwGL3_RenderDrawLists(ImDrawData* draw_data) 27 { 28 // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) 29 auto io = igGetIO(); 30 int fb_width = cast(int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); 31 int fb_height = cast(int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); 32 if (fb_width == 0 || fb_height == 0) 33 return; 34 draw_data.ScaleClipRects(io.DisplayFramebufferScale); 35 36 // Backup GL state 37 GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, cast(GLint*)&last_active_texture); 38 glActiveTexture(GL_TEXTURE0); 39 GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); 40 GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 41 GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); 42 GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); 43 GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); 44 GLint[4] last_viewport; glGetIntegerv(GL_VIEWPORT, last_viewport.ptr); 45 GLint[4] last_scissor_box; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box.ptr); 46 GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, cast(GLint*)&last_blend_src_rgb); 47 GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, cast(GLint*)&last_blend_dst_rgb); 48 GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, cast(GLint*)&last_blend_src_alpha); 49 GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, cast(GLint*)&last_blend_dst_alpha); 50 GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, cast(GLint*)&last_blend_equation_rgb); 51 GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, cast(GLint*)&last_blend_equation_alpha); 52 GLboolean last_enable_blend = glIsEnabled(GL_BLEND); 53 GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); 54 GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); 55 GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); 56 57 // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled 58 glEnable(GL_BLEND); 59 glBlendEquation(GL_FUNC_ADD); 60 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 61 glDisable(GL_CULL_FACE); 62 glDisable(GL_DEPTH_TEST); 63 glEnable(GL_SCISSOR_TEST); 64 65 // Setup viewport, orthographic projection matrix 66 glViewport(0, 0, cast(GLsizei)fb_width, cast(GLsizei)fb_height); 67 const float[4][4] ortho_projection = 68 [ 69 [ 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f ], 70 [ 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f ], 71 [ 0.0f, 0.0f, -1.0f, 0.0f ], 72 [-1.0f, 1.0f, 0.0f, 1.0f ], 73 ]; 74 glUseProgram(g_ShaderHandle); 75 glUniform1i(g_AttribLocationTex, 0); 76 glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); 77 glBindVertexArray(g_VaoHandle); 78 79 for (int n = 0; n < draw_data.CmdListsCount; n++) 80 { 81 ImDrawList* cmd_list = draw_data.CmdLists[n]; 82 ImDrawIdx* idx_buffer_offset; 83 84 auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list); 85 auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list); 86 87 glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); 88 glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW); 89 90 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); 91 glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW); 92 93 auto cmdCnt = ImDrawList_GetCmdSize(cmd_list); 94 95 foreach(cmd_i; 0..cmdCnt) 96 { 97 auto pcmd = ImDrawList_GetCmdPtr(cmd_list, cmd_i); 98 99 if (pcmd.UserCallback) 100 { 101 pcmd.UserCallback(cmd_list, pcmd); 102 } 103 else 104 { 105 glBindTexture(GL_TEXTURE_2D, cast(GLuint)pcmd.TextureId); 106 glScissor(cast(int)pcmd.ClipRect.x, cast(int)(fb_height - pcmd.ClipRect.w), cast(int)(pcmd.ClipRect.z - pcmd.ClipRect.x), cast(int)(pcmd.ClipRect.w - pcmd.ClipRect.y)); 107 glDrawElements(GL_TRIANGLES, cast(GLsizei)pcmd.ElemCount, ImDrawIdx.sizeof == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); 108 } 109 idx_buffer_offset += pcmd.ElemCount; 110 } 111 } 112 113 // Restore modified GL state 114 glUseProgram(last_program); 115 glBindTexture(GL_TEXTURE_2D, last_texture); 116 glActiveTexture(last_active_texture); 117 glBindVertexArray(last_vertex_array); 118 glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); 119 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); 120 glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); 121 glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); 122 if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); 123 if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); 124 if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); 125 if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); 126 glViewport(last_viewport[0], last_viewport[1], cast(GLsizei)last_viewport[2], cast(GLsizei)last_viewport[3]); 127 glScissor(last_scissor_box[0], last_scissor_box[1], cast(GLsizei)last_scissor_box[2], cast(GLsizei)last_scissor_box[3]); 128 } 129 130 extern(C) nothrow const(char)* igImplGlfwGL3_GetClipboardText(void* user_data) 131 { 132 return glfwGetClipboardString(cast(GLFWwindow*)user_data); 133 } 134 135 extern(C) nothrow void igImplGlfwGL3_SetClipboardText(void* user_data, const(char)* text) 136 { 137 glfwSetClipboardString(cast(GLFWwindow*)user_data, text); 138 } 139 140 extern(C) nothrow void igImplGlfwGL3_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) 141 { 142 if (action == GLFW_PRESS && button >= 0 && button < 3) 143 g_MousePressed[button] = true; 144 } 145 146 extern(C) nothrow void igImplGlfwGL3_ScrollCallback(GLFWwindow*, double /*xoffset*/, double yoffset) 147 { 148 g_MouseWheel += cast(float)yoffset; // Use fractional mouse wheel, 1.0 unit 5 lines. 149 } 150 151 extern(C) nothrow void igImplGlfwGL3_KeyCallback(GLFWwindow*, int key, int, int action, int mods) 152 { 153 if(key < 0) return; // ignore the special key code used for Japanese key board 154 auto io = igGetIO(); 155 if (action == GLFW_PRESS) 156 io.KeysDown[key] = true; 157 if (action == GLFW_RELEASE) 158 io.KeysDown[key] = false; 159 160 //(void)mods; // Modifiers are not reliable across systems 161 io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; 162 io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; 163 io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; 164 io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; 165 } 166 167 extern(C) nothrow void igImplGlfwGL3_CharCallback(GLFWwindow*, uint c) 168 { 169 if (c > 0 && c < 0x10000) 170 ImGuiIO_AddInputCharacter(cast(ushort)c); 171 } 172 173 bool igImplGlfwGL3_CreateFontsTexture() 174 { 175 // Build texture atlas 176 auto io = igGetIO(); 177 ubyte* pixels; 178 int width, height; 179 ImFontAtlas_GetTexDataAsRGBA32(io.Fonts, &pixels, &width, &height, null); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. 180 181 // Upload texture to graphics system 182 GLint last_texture; 183 glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 184 glGenTextures(1, &g_FontTexture); 185 glBindTexture(GL_TEXTURE_2D, g_FontTexture); 186 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 188 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 189 190 // Store our identifier 191 ImFontAtlas_SetTexID(io.Fonts, cast(void*)g_FontTexture); 192 193 // Restore state 194 glBindTexture(GL_TEXTURE_2D, last_texture); 195 196 return true; 197 } 198 199 bool igImplGlfwGL3_CreateDeviceObjects() 200 { 201 // Backup GL state 202 GLint last_texture, last_array_buffer, last_vertex_array; 203 glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 204 glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); 205 glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); 206 207 const GLchar *vertex_shader = 208 "#version 330\n" ~ 209 "uniform mat4 ProjMtx;\n" ~ 210 "in vec2 Position;\n" ~ 211 "in vec2 UV;\n" ~ 212 "in vec4 Color;\n" ~ 213 "out vec2 Frag_UV;\n" ~ 214 "out vec4 Frag_Color;\n" ~ 215 "void main()\n" ~ 216 "{\n" ~ 217 " Frag_UV = UV;\n" ~ 218 " Frag_Color = Color;\n" ~ 219 " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" ~ 220 "}\n"; 221 222 const GLchar* fragment_shader = 223 "#version 330\n" ~ 224 "uniform sampler2D Texture;\n" ~ 225 "in vec2 Frag_UV;\n" ~ 226 "in vec4 Frag_Color;\n" ~ 227 "out vec4 Out_Color;\n" ~ 228 "void main()\n" ~ 229 "{\n" ~ 230 " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" ~ 231 "}\n"; 232 233 g_ShaderHandle = glCreateProgram(); 234 g_VertHandle = glCreateShader(GL_VERTEX_SHADER); 235 g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); 236 glShaderSource(g_VertHandle, 1, &vertex_shader, null); 237 glShaderSource(g_FragHandle, 1, &fragment_shader, null); 238 glCompileShader(g_VertHandle); 239 glCompileShader(g_FragHandle); 240 glAttachShader(g_ShaderHandle, g_VertHandle); 241 glAttachShader(g_ShaderHandle, g_FragHandle); 242 glLinkProgram(g_ShaderHandle); 243 244 g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); 245 g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); 246 g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); 247 g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); 248 g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); 249 250 glGenBuffers(1, &g_VboHandle); 251 glGenBuffers(1, &g_ElementsHandle); 252 253 glGenVertexArrays(1, &g_VaoHandle); 254 glBindVertexArray(g_VaoHandle); 255 glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); 256 glEnableVertexAttribArray(g_AttribLocationPosition); 257 glEnableVertexAttribArray(g_AttribLocationUV); 258 glEnableVertexAttribArray(g_AttribLocationColor); 259 260 glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.pos.offsetof); 261 glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof); 262 glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof); 263 264 igImplGlfwGL3_CreateFontsTexture(); 265 266 // Restore modified GL state 267 glBindTexture(GL_TEXTURE_2D, last_texture); 268 glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); 269 glBindVertexArray(last_vertex_array); 270 271 return true; 272 } 273 274 void igImplGlfwGL3_InvalidateDeviceObjects() 275 { 276 if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle); 277 if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle); 278 if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle); 279 g_VaoHandle = g_VboHandle = g_ElementsHandle = 0; 280 281 if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle); 282 if (g_VertHandle) glDeleteShader(g_VertHandle); 283 g_VertHandle = 0; 284 285 if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle); 286 if (g_FragHandle) glDeleteShader(g_FragHandle); 287 g_FragHandle = 0; 288 289 if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle); 290 g_ShaderHandle = 0; 291 292 if (g_FontTexture) 293 { 294 glDeleteTextures(1, &g_FontTexture); 295 ImFontAtlas_SetTexID(igGetIO().Fonts, cast(void*)0); 296 g_FontTexture = 0; 297 } 298 } 299 300 bool igImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks) 301 { 302 g_Window = window; 303 304 auto io = igGetIO(); 305 io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. 306 io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; 307 io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; 308 io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; 309 io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; 310 io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; 311 io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; 312 io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; 313 io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; 314 io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; 315 io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; 316 io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; 317 io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; 318 io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; 319 io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; 320 io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; 321 io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; 322 io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; 323 io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; 324 325 io.RenderDrawListsFn = &igImplGlfwGL3_RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer. 326 io.SetClipboardTextFn = &igImplGlfwGL3_SetClipboardText; 327 io.GetClipboardTextFn = &igImplGlfwGL3_GetClipboardText; 328 io.ClipboardUserData = g_Window; 329 version( Windows ) { 330 DerelictGLFW3_loadNative; 331 io.ImeWindowHandle = glfwGetWin32Window(g_Window); 332 } 333 334 if (install_callbacks) 335 { 336 glfwSetMouseButtonCallback(window, &igImplGlfwGL3_MouseButtonCallback); 337 glfwSetScrollCallback(window, &igImplGlfwGL3_ScrollCallback); 338 glfwSetKeyCallback(window, &igImplGlfwGL3_KeyCallback); 339 glfwSetCharCallback(window, &igImplGlfwGL3_CharCallback); 340 } 341 342 return true; 343 } 344 345 void igImplGlfwGL3_Shutdown() 346 { 347 igImplGlfwGL3_InvalidateDeviceObjects(); 348 igShutdown(); 349 } 350 351 void igImplGlfwGL3_NewFrame() 352 { 353 if (!g_FontTexture) 354 igImplGlfwGL3_CreateDeviceObjects(); 355 356 auto io = igGetIO(); 357 358 // Setup display size (every frame to accommodate for window resizing) 359 int w, h; 360 int display_w, display_h; 361 glfwGetWindowSize(g_Window, &w, &h); 362 glfwGetFramebufferSize(g_Window, &display_w, &display_h); 363 io.DisplaySize = ImVec2(cast(float)w, cast(float)h); 364 io.DisplayFramebufferScale = ImVec2(w > 0 ? (cast(float)display_w / w) : 0, h > 0 ? (cast(float)display_h / h) : 0); 365 366 // Setup time step 367 double current_time = glfwGetTime(); 368 io.DeltaTime = g_Time > 0.0 ? cast(float)(current_time - g_Time) : cast(float)(1.0f/60.0f); 369 g_Time = current_time; 370 371 // Setup inputs 372 // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) 373 if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) 374 { 375 double mouse_x, mouse_y; 376 glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); 377 io.MousePos = ImVec2(cast(float)mouse_x, cast(float)mouse_y); // Mouse position in screen coordinates (set to -1,-1 if no mouse / on another screen, etc.) 378 } 379 else 380 { 381 io.MousePos = ImVec2(-1,-1); 382 } 383 384 for (int i = 0; i < 3; i++) 385 { 386 io.MouseDown[i] = g_MousePressed[i] || glfwGetMouseButton(g_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. 387 g_MousePressed[i] = false; 388 } 389 390 io.MouseWheel = g_MouseWheel; 391 g_MouseWheel = 0.0f; 392 393 // Hide OS mouse cursor if ImGui is drawing it 394 glfwSetInputMode(g_Window, GLFW_CURSOR, io.MouseDrawCursor ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_NORMAL); 395 396 // Start the frame 397 igNewFrame(); 398 }