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 }