module imgui; import glad.gl.all; import derelict.sdl2.sdl; import derelict.imgui.imgui; import window; import std.traits : isDelegate, ReturnType, ParameterTypeTuple; auto bindDelegate(T, string file = __FILE__, size_t line = __LINE__)(T t) if(isDelegate!T) { static T dg; dg = t; extern(C) static ReturnType!T func(ParameterTypeTuple!T args) { return dg(args); } return &func; } // bindDelegate (thanks Destructionator) struct Imgui { // OpenGL data, cleanup soon GLuint font_texture_ = 0; int shander_handle_ = 0, vs_handle_ = 0, fs_handle_ = 0; int shader_attrib_loc_tex_ = 0, shader_attrib_loc_projmtx_ = 0; int shader_attrib_loc_pos_ = 0, shader_attrib_loc_uv_ = 0, shader_attrib_loc_color_ = 0; uint vao_ = 0, vbo_ = 0, ebo_ = 0; // imgui input related state bool[3] mouse_buttons_pressed; float scroll_wheel = 0.0f; void initialize() { auto io = igGetIO(); io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB; io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP; io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN; io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP; io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN; io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME; io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END; io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE; io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE; io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN; io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE; io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A; io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C; io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V; io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X; io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y; io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z; io.RenderDrawListsFn = bindDelegate(&renderDrawLists); io.SetClipboardTextFn = bindDelegate(&setClipboardText); io.GetClipboardTextFn = bindDelegate(&getClipboardText); } void createFontTexture() { // build texture atlas auto io = igGetIO(); ubyte* pixels; int width, height; ImFontAtlas_GetTexDataAsRGBA32(io.Fonts, &pixels, &width, &height, null); // upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGenTextures(1, &font_texture_); glBindTexture(GL_TEXTURE_2D, font_texture_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // store our identifier ImFontAtlas_SetTexID(io.Fonts, cast(void*)font_texture_); // restore state glBindTexture(GL_TEXTURE_2D, last_texture); } void createDeviceObjects() { // backup GL state GLint last_texture, last_array_buffer, last_vertex_array; if (last_texture != 0) glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); if (last_array_buffer != 0) glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); if (last_vertex_array != 0) glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); const GLchar *vertex_shader = q{ #version 330 uniform mat4 ProjMtx; in vec2 Position; in vec2 UV; in vec4 Color; out vec2 Frag_UV; out vec4 Frag_Color; void main() { Frag_UV = UV; Frag_Color = Color; gl_Position = ProjMtx * vec4(Position.xy,0,1); } }; const GLchar* fragment_shader = q{ #version 330 uniform sampler2D Texture; in vec2 Frag_UV; in vec4 Frag_Color; out vec4 Out_Color; void main() { Out_Color = Frag_Color * texture( Texture, Frag_UV.st); } }; shander_handle_ = glCreateProgram(); vs_handle_ = glCreateShader(GL_VERTEX_SHADER); fs_handle_ = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vs_handle_, 1, &vertex_shader, null); glShaderSource(fs_handle_, 1, &fragment_shader, null); glCompileShader(vs_handle_); glCompileShader(fs_handle_); glAttachShader(shander_handle_, vs_handle_); glAttachShader(shander_handle_, fs_handle_); glLinkProgram(shander_handle_); shader_attrib_loc_tex_ = glGetUniformLocation(shander_handle_, "Texture"); shader_attrib_loc_projmtx_ = glGetUniformLocation(shander_handle_, "ProjMtx"); shader_attrib_loc_pos_ = glGetAttribLocation(shander_handle_, "Position"); shader_attrib_loc_uv_ = glGetAttribLocation(shander_handle_, "UV"); shader_attrib_loc_color_ = glGetAttribLocation(shander_handle_, "Color"); glGenBuffers(1, &vbo_); glGenBuffers(1, &ebo_); glGenVertexArrays(1, &vao_); glBindVertexArray(vao_); glBindBuffer(GL_ARRAY_BUFFER, vbo_); glEnableVertexAttribArray(shader_attrib_loc_pos_); glEnableVertexAttribArray(shader_attrib_loc_uv_); glEnableVertexAttribArray(shader_attrib_loc_color_); glVertexAttribPointer(shader_attrib_loc_pos_, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.pos.offsetof); glVertexAttribPointer(shader_attrib_loc_uv_, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof); glVertexAttribPointer(shader_attrib_loc_color_, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof); createFontTexture(); // restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); glBindVertexArray(last_vertex_array); } struct GLState { GLenum last_active_texture; GLint last_program; GLint last_texture; GLint last_array_buffer; GLint last_element_array_buffer; GLint last_vertex_array; GLint[4] last_viewport; GLint[4] last_scissor_box; GLenum last_blend_src_rgb; GLenum last_blend_dst_rgb; GLenum last_blend_src_alpha; GLenum last_blend_dst_alpha; GLenum last_blend_equation_rgb; GLenum last_blend_equation_alpha; GLboolean last_enable_blend; GLboolean last_enable_cull_face; GLboolean last_enable_depth_test; GLboolean last_enable_scissor_test; } nothrow @nogc GLState backupGLState() { GLState gl_state; // backup GL state with (gl_state) { glGetIntegerv(GL_ACTIVE_TEXTURE, cast(GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); glGetIntegerv(GL_VIEWPORT, last_viewport.ptr); glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box.ptr); glGetIntegerv(GL_BLEND_SRC_RGB, cast(GLint*)&last_blend_src_rgb); glGetIntegerv(GL_BLEND_DST_RGB, cast(GLint*)&last_blend_dst_rgb); glGetIntegerv(GL_BLEND_SRC_ALPHA, cast(GLint*)&last_blend_src_alpha); glGetIntegerv(GL_BLEND_DST_ALPHA, cast(GLint*)&last_blend_dst_alpha); glGetIntegerv(GL_BLEND_EQUATION_RGB, cast(GLint*)&last_blend_equation_rgb); glGetIntegerv(GL_BLEND_EQUATION_ALPHA, cast(GLint*)&last_blend_equation_alpha); last_enable_blend = glIsEnabled(GL_BLEND); last_enable_cull_face = glIsEnabled(GL_CULL_FACE); last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); } return gl_state; } nothrow @nogc void restoreGLState(GLState state) { // restore modified GL state with (state) { glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); glActiveTexture(last_active_texture); glBindVertexArray(last_vertex_array); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); glViewport(last_viewport[0], last_viewport[1], cast(GLsizei)last_viewport[2], cast(GLsizei)last_viewport[3]); glScissor(last_scissor_box[0], last_scissor_box[1], cast(GLsizei)last_scissor_box[2], cast(GLsizei)last_scissor_box[3]); } } extern(C) nothrow void renderDrawLists(ImDrawData* draw_data) { // avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) auto io = igGetIO(); int fb_width = cast(int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); int fb_height = cast(int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); if (fb_width == 0 || fb_height == 0) { return; } draw_data.ScaleClipRects(io.DisplayFramebufferScale); // backup GL state GLState last_state = backupGLState(); // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); // Setup viewport, orthographic projection matrix glViewport(0, 0, cast(GLsizei)fb_width, cast(GLsizei)fb_height); const float[4][4] ortho_projection = [ [ 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f ], [ 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f ], [ 0.0f, 0.0f, -1.0f, 0.0f ], [-1.0f, 1.0f, 0.0f, 1.0f ], ]; glUseProgram(shander_handle_); glUniform1i(shader_attrib_loc_tex_, 0); glUniformMatrix4fv(shader_attrib_loc_projmtx_, 1, GL_FALSE, &ortho_projection[0][0]); glBindVertexArray(vao_); foreach (int n; 0 .. draw_data.CmdListsCount) { ImDrawList* cmd_list = draw_data.CmdLists[n]; ImDrawIdx* idx_buffer_offset; auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list); auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list); glBindBuffer(GL_ARRAY_BUFFER, vbo_); glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW); auto cmdCnt = ImDrawList_GetCmdSize(cmd_list); foreach(int cmd_i; 0 .. cmdCnt) { auto pcmd = ImDrawList_GetCmdPtr(cmd_list, cmd_i); if (pcmd.UserCallback) { pcmd.UserCallback(cmd_list, pcmd); } else { glBindTexture(GL_TEXTURE_2D, cast(GLuint)pcmd.TextureId); 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)); glDrawElements(GL_TRIANGLES, cast(GLsizei)pcmd.ElemCount, ImDrawIdx.sizeof == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); } idx_buffer_offset += pcmd.ElemCount; } } // restore old state restoreGLState(last_state); } extern(C) nothrow const(char)* getClipboardText(void* user_data) { return SDL_GetClipboardText(); } extern(C) nothrow void setClipboardText(void* user_data, const (char)* text) { SDL_SetClipboardText(text); } void newFrame(ref Window win) { auto io = igGetIO(); int win_w, win_h; win.windowSize(win_w, win_h); int fbo_w, fbo_h; win.framebufferSize(fbo_w, fbo_h); io.DisplaySize = ImVec2(win_w, win_h); io.DisplayFramebufferScale = ImVec2(cast(float)fbo_w / win_w, cast(float)fbo_h / win_h); int m_x, m_y; win.mousePos(m_x, m_y); io.MousePos = ImVec2(cast(float)m_x, cast(float)m_y); io.MouseDown[0] = mouse_buttons_pressed[0] || (SDL_GetMouseState(null, null) & SDL_BUTTON_LEFT) != 0; mouse_buttons_pressed[0] = false; io.MouseDown[1] = mouse_buttons_pressed[0] || (SDL_GetMouseState(null, null) & SDL_BUTTON_MIDDLE) != 0; mouse_buttons_pressed[1] = false; io.MouseDown[2] = mouse_buttons_pressed[2] || (SDL_GetMouseState(null, null) & SDL_BUTTON_RIGHT) != 0; mouse_buttons_pressed[2] = false; io.MouseWheel = scroll_wheel; scroll_wheel = 0.0f; igNewFrame(); } void endFrame() { igRender(); } void handleEvent(ref SDL_Event ev) { auto io = igGetIO(); switch (ev.type) with (SDL_EventType) { case SDL_TEXTINPUT: ImGuiIO_AddInputCharacter(cast(ushort)ev.text.text[0]); break; case SDL_KEYDOWN, SDL_KEYUP: auto mods = ev.key.keysym.mod; io.KeyCtrl = (mods & KMOD_CTRL) != 0; io.KeyShift = (mods & KMOD_SHIFT) != 0; io.KeyAlt = (mods & KMOD_ALT) != 0; break; case SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP: auto btn = ev.button.button; if (btn < 4) { mouse_buttons_pressed[btn - 1] = (ev.type == SDL_MOUSEBUTTONDOWN); } break; case SDL_MOUSEWHEEL: scroll_wheel += ev.wheel.y; break; default: break; } } }